Store
Key-value store namespaced by `(deviceId, plugin)` taken from the token, never from the path. Values are envelopes when the plugin declares `e2e`.
Keys of the calling (device, plugin) namespace, optionally filtered by prefix. Values are not included.
pluginTokenAuthorizationBearer <token>Token from POST /v1/auth/token (10 minutes, one device + one plugin + services). Channel owner endpoints require the token of the device and plugin that opened the channel.
prefix?stringOnly keys starting with this string.
Matching keys.
application/json- response
items*array<>cursor?stringPresent when there are more keys.
curl -X GET 'https://cloud.example.org/v1/store'{ "items": [ { "key": "string", "version": 1, "updatedAt": 0 } ], "cursor": "string"}pluginTokenAuthorizationBearer <token>Token from POST /v1/auth/token (10 minutes, one device + one plugin + services). Channel owner endpoints require the token of the device and plugin that opened the channel.
key*stringPath-like key (/ allowed, each segment URL-encoded), no . or .. segments.
^[A-Za-z0-9._:/-]{1,256}$The value with its version.
application/json- response
key*StoreKeyPath-like key; no . or .. segments.
^[A-Za-z0-9._:/-]{1,256}$value?unknownAny JSON value; an Envelope when the plugin declares e2e.
version*integer1 <= valueupdatedAt*MsUnix time in milliseconds.
0 <= valueexpiresAt?MsUnix time in milliseconds.
0 <= valuecurl -X GET 'https://cloud.example.org/v1/store/string'{ "key": "string", "value": null, "version": 1, "updatedAt": 0, "expiresAt": 0}pluginTokenAuthorizationBearer <token>Token from POST /v1/auth/token (10 minutes, one device + one plugin + services). Channel owner endpoints require the token of the device and plugin that opened the channel.
key*stringPath-like key (/ allowed, each segment URL-encoded), no . or .. segments.
^[A-Za-z0-9._:/-]{1,256}$Deleted.
application/json- response
ok*booleancurl -X DELETE 'https://cloud.example.org/v1/store/string'{ "ok": true}Creates or replaces a value. Optimistic concurrency with If-Match: <version>: the write
fails with 409 conflict unless the stored version matches; If-Match: 0 means the key must
not exist yet. Default quotas: 1 MB and 1,000 keys per plugin per device, 64 KB per value
(limits.store in GET /v1/meta).
pluginTokenAuthorizationBearer <token>Token from POST /v1/auth/token (10 minutes, one device + one plugin + services). Channel owner endpoints require the token of the device and plugin that opened the channel.
key*stringPath-like key (/ allowed, each segment URL-encoded), no . or .. segments.
^[A-Za-z0-9._:/-]{1,256}$If-Match?stringExpected current version; 0 = the key must not exist.
^[0-9]+$application/json- body
value?unknownAny JSON value; an Envelope when the plugin declares e2e.
ttlS?integerExpire the value after this many seconds.
1 <= valueThe new version (also sent as the ETag header).
application/json- response
version*integer1 <= valuecurl -X PUT 'https://cloud.example.org/v1/store/string' \ -H 'Content-Type: application/json' \ -d '{}'{ "version": 1}Devices and auth
A device's cloud identity is an Ed25519 signing key plus an X25519 box key, nothing else: no student ID, no name. Registration costs a proof of work; plugin tokens are minted from a signed challenge and live 10 minutes.
Channels (owner)
End-to-end encrypted relay channels opened by a device, used for live sharing with people who open a share link. The owner talks to its peers over a WebSocket; the server only forwards envelopes.