Authentication
Skytale has three credentials, each deliberately weaker than the last: an account API key (the root credential), a short-lived JWT (what the relay actually accepts), and a channel-scoped device-grant key (what a seat holds so it never needs your root key). None of them is a decryption key. Authentication in Skytale controls who can move ciphertext and operate the directory; reading content is controlled by MLS group membership and your seed, which no server holds.
This page explains each credential. The full endpoint reference lives at Directory and relay API.
Account API keys
Section titled “Account API keys”The root credential is an API key with the sk_live_ prefix. Your first key is minted when the account is created: POST /v1/accounts is unauthenticated, takes an email, and returns the account plus its first key.
curl -s -X POST https://api.skytale.sh/v1/accounts \ -H "Content-Type: application/json" \ -d '{"email": "<you@example.com>"}'The raw key is returned exactly once, at mint. The server stores only a hash of it, so a lost key cannot be recovered, only replaced. Additional keys are minted with POST /v1/keys and revoked with DELETE /v1/keys/{id}, both authenticated with an existing key as a Bearer token.
The account key authenticates the directory surface: channel registration, invites, the owner side of the join handshake. Treat it like a password; the standard hygiene applies (environment variable or keychain, never version control).
Relay JWTs
Section titled “Relay JWTs”The relay does not accept API keys. Every relay request (the WebSocket, GET /v1/messages, GET /v1/sync) carries a short-lived JWT minted by the directory:
curl -s -X POST https://api.skytale.sh/v1/tokens \ -H "Authorization: Bearer sk_live_<your-key>"{"token":"eyJhbGciOiJIUzI1NiIs..."}The token is HS256 with iss: skytale-api, sub = your account UUID, and an expiry the API caps at 300 seconds. Pass it as Authorization: Bearer <JWT> on HTTP, or in the first frame on the WebSocket. Clients re-exchange when it expires; the SDK does this for you.
Two honest notes on what a relay JWT authorizes:
- A valid JWT plus a well-formed channel name is sufficient to read or write any channel’s ciphertext on the relay. The relay has no account-to-channel table; the ownership gate lives in the directory, and the content gate lives in the MLS group itself (you cannot decrypt a channel you were never added to).
- The relay honors revocation: a revoked identity gets
403 Forbidden.
The device grant
Section titled “The device grant”The device grant answers a specific problem: a device or agent seat needs to sync on its own channel, but handing it your account key would let it do everything your account can do, on every channel, forever. The grant gets a seat a channel-scoped credential without the owner key ever touching the device. tally connect is the consumer of this flow; it runs the grant and stores the resulting credential so tally push and tally pull work.
The flow is RFC-8628-style device authorization, three endpoints:
POST /v1/device/code(unauthenticated): the device starts a request, submits its MLS key package upfront, and receives adevice_code(secret, kept by the device) plus a short human-readableuser_code(renderedXXXX-XXXX). The request expires after 10 minutes.POST /v1/device/approve(owner-authenticated): the owner approves theuser_code, naming the channel and seat. Approval also files the device’s key package as a join request, so the owner’s normal add-member flow admits the seat into the MLS group. No credential is minted at approval.POST /v1/device/token(unauthenticated): the device polls with itsdevice_codeat the advised 5-second interval. On the first poll after approval, a channel-scopedsk_live_key is minted and returned. Re-polls return the same key; the poll is idempotent.
The scoping is enforced server-side: the minted key carries its channel, and the API refuses it on any other channel and on the account-wide surface. A leaked seat credential is a one-channel transport credential, not your account.
The mint-at-token design is also a storage property: the credential is minted at redemption and stored sealed under a key derived from the device’s own device_code, which the server keeps only as a hash. The database holds hashes and sealed bytes; a database compromise does not yield usable credentials.
A grant is revoked with DELETE /v1/device/grant, which kills the seat’s key. Removing the seat from the MLS group is the separate, cryptographic half of offboarding; do both.
The chain, end to end
Section titled “The chain, end to end”An owner key mints JWTs and approves grants. A grant mints a seat’s channel-scoped key. That scoped key mints JWTs that only make sense for one channel. And at the bottom of every chain, the thing being moved is ciphertext either way; the credential hierarchy bounds blast radius, and the encryption bounds what any credential is worth.
Where next
Section titled “Where next”- Directory and relay API: every endpoint, with request and response shapes
- Architecture: the two planes these credentials authenticate to
- tally connect: the CLI consumer of the device grant