Reference
Everything about the HTTP API of U00 on one page: how a channel is made, how values are written and read, and the limits.
Overview
U00 keeps channels of values. A device or a script writes items of a channel with plain HTTP requests; anyone who knows the public ID of the channel watches it live in a browser or reads it with the API.
GET /set/KEY?/temp=21.5──▶GET /ch/CHANNEL_ID◀──- No sign-up: a channel is made by its key on the first write.
- No agent and no SDK: any HTTP client works. An agent that sends a lot may use the binary protocol instead: the same calls over a kept TLS connection.
- Numbers are kept as a history and drawn as a chart; text is shown as it is.
Quick start
# 1. A key: 16 random bytes in hex. Keep it: it is the secret of the channel
KEY=$(openssl rand -hex 16)
# 2. Write a value and its title
curl "{ORIGIN}/set/$KEY?/=21.5&/_name=Temperature"
# 3. The page of the channel
curl "{ORIGIN}/view_url_by_api_key/$KEY"
A step-by-step walk-through with a key made in the browser is in Get started; the playground shows the requests and the answers live.
Keys and channel IDs
| API key | Channel ID | |
|---|---|---|
| What | 16 random bytes, 32 hex characters | the first 16 bytes of SHA3-256 of the key bytes, 32 hex characters |
| Gives | writing to the channel | reading the channel |
| Keep it | secret | public: share it |
| Example | 25b50e253fd2d95b7a7d1ccae4eaf978 | cdd81a950f282f6d40fd0fb7d6965dca |
The ID is derived from the key and the key can not be got from the ID, so a shared link to a channel can never write to it. There is no recovery: a lost key means a new channel.
Make a key
openssl rand -hex 16
import secrets print(secrets.token_hex(16))
Get the channel ID of a key
# bash or zsh
printf "$(echo $KEY | sed 's/../\\x&/g')" | openssl dgst -sha3-256 | awk '{print substr($NF, 1, 32)}'
import hashlib channel_id = hashlib.sha3_256(bytes.fromhex(KEY)).hexdigest()[:32]
// Node.js
const channelId = require('crypto').createHash('sha3-256').update(Buffer.from(KEY, 'hex')).digest('hex').slice(0, 32);
Or ask the server: GET /view_url_by_api_key/<key> answers the URL of the page of the
channel.
Items
A channel is a set of items: name → value. The names are paths by convention.
| Name | Meaning |
|---|---|
/ | the main value of the channel: the page shows it first |
/temperature, /room1/humidity | any other items; the path groups them |
/_name | the title of the channel |
/room1/humidity/_name | the title of the item /room1/humidity |
/_source, /_source_url | where the data comes from: shown under the title of the chart |
/_license, /_license_url | the license of the data; <item>/_source … for one item |
/_quality | the data can be trusted: ok, bad (not to be trusted), nodata (none: a sensor gone); /_quality_note - why. The chart shows the times not valid as red areas |
/_type | the type of the data: auto (the default: a number goes to the history), text (no history), geo (coordinates: a map) |
/_period | how often the data comes, seconds: the freshness of the value and the default range of the chart are of it (send it for slow data: every 15 min - 900) |
- A name is up to 64 bytes, a value up to 10 KB.
- The names with
/_are service items: not listed as data; the button i of the chart shows them. Send them along with the data, like the values. - A channel has up to 1000 items at a time; up to 1000 of them get a history in an hour.
- A value is a string. A value that is a number and shorter than 16 characters is also put to the history of the item, with its time in milliseconds, and drawn on the chart.
- The time of a value is the time the server got it.
Coordinates
An item of the type geo is a position: geo:55.7558,37.6173 (a geo URI, RFC 5870;
the altitude in meters may follow: geo:55.7558,37.6173,150) or just 55.7558,37.6173 -
the latitude first. The page of the channel shows it on a map of OpenStreetMap with its track of the range
chosen; the cards show a small map.
curl -X POST "{ORIGIN}/set/KEY" \
-d "/gps=geo:55.7558,37.6173" \
-d "/gps/_type=geo" \
-d "/gps/_name=Courier"
- The track is the history of the latitude and the longitude:
/gps/_lat,/gps/_lon(and/gps/_alt) - read as any history. - The channel is public: a position written is seen by anyone with its ID.
Bad data and no data
A sensor gone wrong or gone: tell it with /_quality - the chart shows that time as a red
hatched area, its values are left out of the line and of the scale.
# right
curl "{ORIGIN}/set/KEY?/=23.5&/_quality=ok"
# wrong values: kept, not to be trusted
curl "{ORIGIN}/set/KEY?/=999&/_quality=bad&/_quality_note=sensor%20glitch"
# no data: the quality without the value
curl "{ORIGIN}/set/KEY?/_quality=nodata&/_quality_note=sensor%20disconnected"
- Send
/_qualitywith every request while you use it,oktoo. <item>/_quality- for one item;/_quality- for the whole channel.
Views and thresholds
/_view shows an item another way than a chart; the thresholds color the value and mark the
charts.
/_view | Shows | With |
|---|---|---|
gauge | a dial with zones | /_min, /_max |
bar | a level: a tank, a battery | /_min, /_max |
kpi | a big number, its unit under it, a dim chart behind | /_unit |
timeline | states in time and their shares | /_states=0:Off,1:Run,2:Fault |
compass | an angle: an arrow and a rose of directions | |
array | numbers 1.2,3.4,… as bars: a spectrum |
curl -X POST "{ORIGIN}/set/KEY" \
-d "/=64.5" -d "/_view=gauge" -d "/_min=0" -d "/_max=100" \
-d "/_warn=70" -d "/_alarm=90" -d "/_unit=%"
- Thresholds:
/_warn,/_alarm(over them),/_warn_low,/_alarm_low(under them) - the value turns yellow or red, the chart shows dashed lines. /_unit- the unit of the value.- For one item:
/temp/_view,/temp/_warn, … - The button next to (i) on the channel page switches the view for a viewer; it is not saved - a reload shows the view of the channel.
Writing
Every parameter of the query, or of a form (application/x-www-form-urlencoded), is an item:
its name → its value. One request writes any number of items.
# The query
curl "{ORIGIN}/set/KEY?/=21.5&/humidity=40"
# A form: the same, easier to encode
curl -X POST "{ORIGIN}/set/KEY" \
-d "/=21.5" \
-d "/humidity=40" \
-d "/humidity/_name=Humidity, %"
import requests
requests.post(f"{ORIGIN}/set/{KEY}", data={"/": 21.5, "/humidity": 40})
- The answer is
200with an empty body, at once: the values are stored in the background. A bad key, a value over the limits or a new item over the 1000 of the channel is dropped there, without an error. - A request has up to 1000 items and a body up to 64 KB; over them
the answer is
400or413and nothing is written. 429 Too Many Requests- over the rate limit (see Limits).503- the storage is down or overloaded: the values are not written, send them again later.- Encode the values in the query (
%20,%26, …); the/of the names may stay as it is.
Reading
The answers are JSON. A client that sends Accept-Encoding: gzip (every
browser does) gets it in gzip: the browser and the HTTP libraries unpack it themselves. The page of a
channel reads the same API.
Current values
{
"values": [
{ "name": "/", "value": { "dt": 1791100067269, "value": "23.5" } },
{ "name": "/_name", "value": { "dt": 1791100067269, "value": "Temperature" } }
],
"dt_last_set": 1791100067,
"dt_server": 1791100070
}
values- the items, sorted by name;dt- the time of the value, unix ms.dt_last_set- the last write to the channel,dt_server- the time of the server; both unix seconds.404- no such channel.
History
The history is kept in blocks of one hour (UTC). The answer is the block of the hour that has
dt - any time of it, rounded down to the start of the hour: ask hour after hour for a longer
range.
{ "history": { "values": [ { "dt": 1791100067269, "value": 23.5 }, { "dt": 1791100068270, "value": 23.6 } ] } }
- The item is the path after the channel ID:
/history/<id>- the item/,/history/<id>/room1/humidity- the item/room1/humidity. - No
dt(or0) - the current hour. An hour with no values is an empty history. 400-dtis not a number;404- no such channel.
Examples
curl -s --compressed "{ORIGIN}/get/CHANNEL_ID"
curl -s --compressed "{ORIGIN}/history/CHANNEL_ID/room1/humidity"
import time, requests
values = requests.get(f"{ORIGIN}/get/{CHANNEL_ID}").json()
hour = requests.get(f"{ORIGIN}/history/{CHANNEL_ID}?dt={int(time.time() * 1000)}").json()
const values = await (await fetch(`/get/${channelId}`)).json();
The page of a key
The URL of the page of the channel of the key, as text. 400 - not a key.
The channel page
/ch/<channel id> shows the channel live: the current value, its age and a chart of the
history.
- The ranges: 5 minutes to 2 weeks; the mouse wheel zooms around the pointer, dragging moves in time.
- The items of the channel are listed beside the chart (under it on a phone); a click shows an item.
- ☆ keeps the channel in your profile: folders of channels with their charts. The profile is kept in this browser.
Limits
The rate limits are by the address of the client; over them the answer is 429.
| Calls | Per second | Burst |
|---|---|---|
/set | 10 | 20 |
/get | 50 | 100 |
/history | 40 | 80 |
pages: /ch, the site | 50 | 100 |
| Item | Limit |
|---|---|
| name | 1 to 64 bytes |
| value | 10 KB |
items of a request of /set | 1000; the body up to 64 KB |
| items of a channel | 1000 at a time (an item not written for a minute leaves its place) |
| items of a channel with a history | 1000 in an hour; the others keep only the current value |
| a value put to the history | a number shorter than 16 characters |
| the history of an item | 10 values a second, a burst of 20; the others keep only the current value |
| history | 14 days; older hours are removed |
Status codes
| Code | When |
|---|---|
200 | done; /set answers it at once with an empty body |
400 | /view_url_by_api_key: not a key; /set: not a key, more than 1000 items; /history: dt is not a number |
404 | no such channel |
413 | /set: the body is over 64 KB |
429 | over the rate limit: wait and send again |
503 | /set: the storage is down or overloaded, the values are not written: send them again later |
Using the API from your site
The API is open to the pages of any site: /set, /get,
/history and /view_url_by_api_key answer
Access-Control-Allow-Origin: *. A dashboard, a widget or the web page of a device reads the
channels right from the browser - no server of your own in between.
// On any site: the current values of a channel
const response = await fetch('{ORIGIN}/get/CHANNEL_ID');
const { values } = await response.json();
- No cookies and no credentials: none are needed.
- Writing from a page needs the key in the page: anyone who opens the page can read it there. Write from a page only to a channel you are fine to share for writing.
- The rate limits are by the address of each visitor.