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.

device / script
writes with the key
GET /set/KEY?/temp=21.5──▶
U00
keeps the values and the history
GET /ch/CHANNEL_ID◀──
browser / API client
reads with the public 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 keyChannel ID
What16 random bytes, 32 hex charactersthe first 16 bytes of SHA3-256 of the key bytes, 32 hex characters
Giveswriting to the channelreading the channel
Keep itsecretpublic: share it
Example25b50e253fd2d95b7a7d1ccae4eaf978cdd81a950f282f6d40fd0fb7d6965dca

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.

NameMeaning
/the main value of the channel: the page shows it first
/temperature, /room1/humidityany other items; the path groups them
/_namethe title of the channel
/room1/humidity/_namethe title of the item /room1/humidity
/_source, /_source_urlwhere the data comes from: shown under the title of the chart
/_license, /_license_urlthe license of the data; <item>/_source … for one item
/_qualitythe 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
/_typethe type of the data: auto (the default: a number goes to the history), text (no history), geo (coordinates: a map)
/_periodhow 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.
NoteThe current values hold the items written lately: an item not written for about a minute may be left out of them while the other items of the channel go on. Its history stays.

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 /_quality with every request while you use it, ok too.
  • <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.

/_viewShowsWith
gaugea dial with zones/_min, /_max
bara level: a tank, a battery/_min, /_max
kpia big number, its unit under it, a dim chart behind/_unit
timelinestates in time and their shares/_states=0:Off,1:Run,2:Fault
compassan angle: an arrow and a rose of directions
arraynumbers 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

GETPOST/set/<key>

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 200 with 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 400 or 413 and 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

GET/get/<channel id>
{
  "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

GET/history/<channel id>[/<item>]?dt=<unix ms>

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 (or 0) - the current hour. An hour with no values is an empty history.
  • 400 - dt is 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

GET/view_url_by_api_key/<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.

CallsPer secondBurst
/set1020
/get50100
/history4080
pages: /ch, the site50100
ItemLimit
name1 to 64 bytes
value10 KB
items of a request of /set1000; the body up to 64 KB
items of a channel1000 at a time (an item not written for a minute leaves its place)
items of a channel with a history1000 in an hour; the others keep only the current value
a value put to the historya number shorter than 16 characters
the history of an item10 values a second, a burst of 20; the others keep only the current value
history14 days; older hours are removed
BatchingMany values at once? Send them in one request as several items rather than many requests: the limit is on requests, not on items.

Status codes

CodeWhen
200done; /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
404no such channel
413/set: the body is over 64 KB
429over 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.
Required: show the sourceWherever data of U00 is shown - on your site, in an app, on a device - the people who see it must see that it comes from U00: a visible "Data: U00" with a link to U00 near the data. The embedded chart does it itself; see Showing the source.