PKU harness
CloudAPI reference

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.

Open a channel

POST
/v1/channels

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).

Authorization

pluginToken
headerAuthorizationBearer <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.

Request Body

application/json
  1. body
ttlS*integer
Range1 <= value
maxPeers?integer
Range1 <= value
Default1
joinHash*Key32

32 bytes, base64url (Ed25519 or X25519 public key, hash).

Match^[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*Key32

32 bytes, base64url (Ed25519 or X25519 public key, hash).

Match^[A-Za-z0-9_-]{43}$
page?PageName
Match^[a-z0-9][a-z0-9-]{0,40}$

Response Body

The channel id and expiry.

application/json
  1. response
id*ChannelId

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$
expiresAt*Ms

Unix time in milliseconds.

Range0 <= value
Example Request
curl -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"}'
Example Response
{  "id": "string",  "expiresAt": 0}

Extend a channel

PATCH
/v1/channels/{id}

Sets the expiry to now + ttlS, capped at limits.channels.maxTtlS. Owner only.

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$

Request Body

application/json
  1. body
ttlS*integer
Range1 <= value

Response Body

The new expiry.

application/json
  1. response
expiresAt*Ms

Unix time in milliseconds.

Range0 <= value
Example Request
curl -X PATCH 'https://cloud.example.org/v1/channels/string' \  -H 'Content-Type: application/json' \  -d '{  "ttlS": 1}'
Example Response
{  "expiresAt": 0}

Close a channel

DELETE
/v1/channels/{id}

Closes the channel; connected peers receive { t: "closed", reason: "owner" }. Owner only.

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$

Response Body

Closed.

application/json
  1. response
ok*boolean
Example Request
curl -X DELETE 'https://cloud.example.org/v1/channels/string'
Example Response
{  "ok": true}

Owner WebSocket

GET
/v1/channels/{id}/ws

Upgrades to a WebSocket for the channel owner. Every frame is one JSON object.

  • Server → owner: OwnerInFrame (hello, join, leave, peer presence, msg, closed, error, pong).
  • Owner → server: OwnerOutFrame (msg to one peer, kick a 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).

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$

Response Body

Switching to the WebSocket protocol. Frames are described by the OwnerInFrame and OwnerOutFrame schemas.

Example Request
curl -X GET 'https://cloud.example.org/v1/channels/string/ws'
Example Response
Empty

Clear the mailbox

DELETE
/v1/channels/{id}/mailbox

Removes every mailbox slot of the channel (Channel.mailbox.clear() in the SDK). Owner only.

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$

Response Body

Cleared.

application/json
  1. response
ok*boolean
Example Request
curl -X DELETE 'https://cloud.example.org/v1/channels/string/mailbox'
Example Response
{  "ok": true}

Delete a mailbox slot

DELETE
/v1/channels/{id}/mailbox/{slot}

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$
slot*string
Match^[A-Za-z0-9._:-]{1,64}$

Response Body

Deleted.

application/json
  1. response
ok*boolean
Example Request
curl -X DELETE 'https://cloud.example.org/v1/channels/string/mailbox/string'
Example Response
{  "ok": true}
PUT
/v1/channels/{id}/mailbox/{slot}

Batch mode. Sealed blobs that peers can read while the owner is offline (at most limits.channels.mailboxSlots slots). Owner only.

Authorization

pluginToken
headerAuthorizationBearer <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.

Path Parameters

id*string

Server-generated, base64url (16 random bytes → 22 characters).

Match^[A-Za-z0-9_-]{8,64}$
slot*string
Match^[A-Za-z0-9._:-]{1,64}$

Request Body

application/json
  1. 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*integer
Range1 <= value

Response Body

Stored.

application/json
  1. response
ok*boolean
Example Request
curl -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}'
Example Response
{  "ok": true}