API and library reference
All routes are relative to the configured instance base URL. HTTPS is required outside loopback development. JSON responses carry cache-control: no-store; redirects are rejected by the library and credentials stay on the selected origin. Send bearer tokens in Authorization and JSON using Content-Type: application/json.
Instance/account operations
| Library | Wire operation | Authority |
|---|---|---|
client.info() | GET /api/info | Anonymous capabilities and limits |
client.bootstrap(credentials) | POST /api/bootstrap | Operator bootstrap token, first owner only |
client.register(credentials) | POST /api/register | Named invitation or enabled registration challenge |
client.login(username,password) | POST /api/challenge, then /api/login | Password and existing device signature |
client.inviteAccount(username) | account.invite through /api/server/{tickets,commands} | Existing instance owner |
client.channels() | GET /api/rooms | Existing deployment's authorized channels |
client.createChannel(properties) | room.create through server tickets/commands | Existing instance owner |
client.joinChannel(id) | room.join through room tickets/commands | Current channel access |
client.sendMessage(id,text,options) | message.send through room tickets/commands | Current admission and recipient revision |
client.history(id,after,includeNonVisible) | GET /api/rooms/<id>/history | Current admission and historical eligibility |
Library registration credentials are {username,password,token}. Device material comes from the constructor's keys value. makeKeys() explicitly generates device keys; the library never generates replacement keys during login.
Logical servers
| Library | Wire operation |
|---|---|
client.servers() | GET /api/servers |
client.createServer(name) | server.create through /api/servers/{tickets,commands} |
client.server(id) | Local handle; no network request |
server.state() | GET /api/servers/<id>/state |
server.settings(changes) | server.settings through server tickets/commands |
server.setMember(accountId,role) | member.set through server tickets/commands |
server.channels() | GET /api/servers/<id>/channels |
server.createChannel(properties) | channel.create through server tickets/commands |
server.channelState(channelId) | GET /api/servers/<id>/channels/<channelId>/state |
server.joinChannel(channelId) | channel.join through channel tickets/commands |
server.sendMessage(channelId,text,options) | message.send through channel tickets/commands |
server.history(channelId,after,includeNonVisible) | GET .../channels/<channelId>/history |
Owners set membership to owner, moderator, member or null (remove). The last owner cannot be removed/demoted. Membership requires an enabled local instance account. Members cannot list or probe other servers. Server settings accept name, pttRequired, pttMaxSeconds; omission preserves values, and the existing 10–600 second PTT range applies.
Channel creation uses the existing property validator: required name, preset (text|voice|combined) and access (public|private); optional description, encryption, parentId, inheritPermissions, members, pttRequired, pttMaxSeconds. Name is trimmed, 1–80 code points with no ASCII controls; description is at most 500 code points. Public encryption defaults to server_readable, private to e2ee; encryption is immutable. Inheritance defaults true. Text-only channels reject PTT fields. The canonical specification sources own complete configuration validation/defaults.
Low-level channel commands also provide channel.leave with {}, channel.settings with {name}, and owner-only channel.permission with {permissions:{inherit,permits,denials}}. Use server.api.command(action,payload,revision,channelId) with an observed revision. SPEC-022's denial precedence and inheritance rules apply. Up to 100 logical servers exist per instance, 100 channels per logical server, eight channel levels, and ten admitted accounts per channel. Instance account/history quotas are shared.
Commands, failures and retries
{
"ticket": "opaque-purpose-bound-token",
"action": "server.settings",
"expectedRevision": 3,
"payload": { "name": "Build systems" }
}
First obtain a ticket by POSTing {} to the same scope's /tickets, then submit the command to /commands. Tickets bind instance audience, account, device, resource scope, UUID and five-minute validity. Results remain available for 24 hours under current authorization. Changing the action, revision or payload on a consumed ticket returns a conflict.
APIError exposes status, code, and a safe message. Statuses include 400 invalid input, 401 invalid/expired session, 403 forbidden, 404 unknown/inaccessible resource, 409 stale revision/conflict/capacity, 410 expired command, 413 oversized input, 429 rate limit and 503 unavailable. Refresh stale state before constructing a new command; never reinterpret a network timeout as rollback.
The library serializes one mutation per API handle and retries transport/server failures once using the identical ticket and payload. An unresolved command remains in api.pending; reconcile with api.retryPending(). Persist the pending record securely before process exit if recovery across restart is required. Restore it only on the same service/account/device. For server creation use client.retryServerCreation(). Do not submit a new creation to recover an unknown result.
client.api.request(path,body,options) provides existing low-level APIs; server.api provides the corresponding logical-server scope. Request options include signal and timeoutMS (15,000 ms default). The library deliberately does not implement a key vault, profile import, automatic session renewal, device approval UI, audio capture or a media stack. Protocol sources cover the other existing extension APIs.