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.
Opens a relay channel owned by the calling device and plugin. The client generates the share
secret k, derives authKey = HKDF(k, "pku-harness-cloud/v1/join") and
encKey = HKDF(k, "pku-harness-cloud/v1/enc"), and sends only joinHash = base64url(sha256(authKey)),
the channel's X25519 public key and meta sealed with encKey. The server can gate joins
but can't read anything. Limits: 5 open channels per device, TTL 60 s – 24 h (default 15 min).
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.
application/json- body
ttlS*integer1 <= valuemaxPeers?integer1 <= value1joinHash*Key3232 bytes, base64url (Ed25519 or X25519 public key, hash).
^[A-Za-z0-9_-]{43}$meta*XChaCha20-Poly1305 sealed JSON. n = 24-byte random nonce, c = ciphertext ‖ 16-byte tag. Open it with open() from @pku-harness/cloud-protocol and validate the result.
chanPub*Key3232 bytes, base64url (Ed25519 or X25519 public key, hash).
^[A-Za-z0-9_-]{43}$page?PageName^[a-z0-9][a-z0-9-]{0,40}$The channel id and expiry.
application/json- response
id*ChannelIdServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$expiresAt*MsUnix time in milliseconds.
0 <= valuecurl -X POST 'https://cloud.example.org/v1/channels' \ -H 'Content-Type: application/json' \ -d '{ "ttlS": 1, "joinHash": "string", "meta": { "v": 1, "n": "string", "c": "stringstringstringstri" }, "chanPub": "string"}'{ "id": "string", "expiresAt": 0}Sets the expiry to now + ttlS, capped at limits.channels.maxTtlS. Owner only.
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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$application/json- body
ttlS*integer1 <= valueThe new expiry.
application/json- response
expiresAt*MsUnix time in milliseconds.
0 <= valuecurl -X PATCH 'https://cloud.example.org/v1/channels/string' \ -H 'Content-Type: application/json' \ -d '{ "ttlS": 1}'{ "expiresAt": 0}Closes the channel; connected peers receive { t: "closed", reason: "owner" }. Owner only.
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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$Closed.
application/json- response
ok*booleancurl -X DELETE 'https://cloud.example.org/v1/channels/string'{ "ok": true}Upgrades to a WebSocket for the channel owner. Every frame is one JSON object.
- Server → owner:
OwnerInFrame(hello,join,leave,peerpresence,msg,closed,error,pong). - Owner → server:
OwnerOutFrame(msgto one peer,kicka peer and free its seat,ping).
Frames carry envelopes sealed with the per-peer key
HKDF(X25519(chanPriv, peerPub) ‖ encKey, salt = chanPub ‖ peerPub, info = "pku-harness-cloud/v1/peer").
Limits: frames ≤ 4 KB of ciphertext, ≤ 60 frames per minute per party (the owner gets
60 × maxPeers); a violating frame is dropped with an error frame. A frame to an offline
party is dropped with error: conflict (use the mailbox for offline delivery).
The token may also be passed as ?t= (WebSocket clients can't always set headers). Close
codes: 4003 kicked, 4009 replaced by a newer connection of the same party, 4010 channel
closed or expired (a closed frame comes first), 1001 server shutdown (reconnect).
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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$Switching to the WebSocket protocol. Frames are described by the OwnerInFrame and OwnerOutFrame schemas.
curl -X GET 'https://cloud.example.org/v1/channels/string/ws'Removes every mailbox slot of the channel (Channel.mailbox.clear() in the SDK). Owner only.
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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$Cleared.
application/json- response
ok*booleancurl -X DELETE 'https://cloud.example.org/v1/channels/string/mailbox'{ "ok": true}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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$slot*string^[A-Za-z0-9._:-]{1,64}$Deleted.
application/json- response
ok*booleancurl -X DELETE 'https://cloud.example.org/v1/channels/string/mailbox/string'{ "ok": true}Batch mode. Sealed blobs that peers can read while the owner is offline (at most limits.channels.mailboxSlots slots). Owner only.
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.
id*stringServer-generated, base64url (16 random bytes → 22 characters).
^[A-Za-z0-9_-]{8,64}$slot*string^[A-Za-z0-9._:-]{1,64}$application/json- body
env*XChaCha20-Poly1305 sealed JSON. n = 24-byte random nonce, c = ciphertext ‖ 16-byte tag. Open it with open() from @pku-harness/cloud-protocol and validate the result.
ttlS*integer1 <= valueStored.
application/json- response
ok*booleancurl -X PUT 'https://cloud.example.org/v1/channels/string/mailbox/string' \ -H 'Content-Type: application/json' \ -d '{ "env": { "v": 1, "n": "string", "c": "stringstringstringstri" }, "ttlS": 1}'{ "ok": true}Store
Key-value store namespaced by `(deviceId, plugin)` taken from the token, never from the path. Values are envelopes when the plugin declares `e2e`.
Channels (peer)
What a viewer (a web page or another app) calls after opening a share link `https://<provider>/s/<id>#k=<secret>`. The fragment `k` never reaches the server; the viewer derives `authKey` from it to join.