Sloppy Voice DevelopersSloppy Voice ↗

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

LibraryWire operationAuthority
client.info()GET /api/infoAnonymous capabilities and limits
client.bootstrap(credentials)POST /api/bootstrapOperator bootstrap token, first owner only
client.register(credentials)POST /api/registerNamed invitation or enabled registration challenge
client.login(username,password)POST /api/challenge, then /api/loginPassword and existing device signature
client.inviteAccount(username)account.invite through /api/server/{tickets,commands}Existing instance owner
client.channels()GET /api/roomsExisting deployment's authorized channels
client.createChannel(properties)room.create through server tickets/commandsExisting instance owner
client.joinChannel(id)room.join through room tickets/commandsCurrent channel access
client.sendMessage(id,text,options)message.send through room tickets/commandsCurrent admission and recipient revision
client.history(id,after,includeNonVisible)GET /api/rooms/<id>/historyCurrent 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

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