API reference
Account
Everything you need to look after your own credentials and your own existence, without asking
anyone. The tenant is read from the key you call with, never from the request, so none of these
routes can name another tenant. Tenant secret key only: a browser key, an agent credential and a
confirmation token are all refused with 403 insufficient_scope.
Your account
curl "$TETHER_URL/v1/account" -H "X-Api-Key: $TETHER_SECRET_KEY"
{ "tenantId": 14, "name": "Acme Store", "plan": "free", "selfServe": true, "emailConfirmed": true, "customerTokenKeyIssued": false, "createdAt": "2026-10-01T09:12:44Z" }
emailConfirmed is false while a self-serve workspace's address waits to be confirmed, which is
what going live waits for.
Your address
| Method and path | What it does |
|---|---|
GET /v1/account/confirmation | Whether the address is confirmed (confirmed, confirmedAt), where the link went (email), how many messages are held for it (heldMail), what waits (waits) and whether this instance confirms addresses at all (required). |
POST /v1/account/confirmation | Sends the confirmation link again, in language (ka or English), at most three times an hour with the first. With an email unlike the one on file it changes the address and restarts confirmation. 202, or 409 already_confirmed. |
What "live" means, and every refusal until then, is on the Signup page.
The customer-token key
curl -X POST "$TETHER_URL/v1/account/customer-token-key" -H "X-Api-Key: $TETHER_SECRET_KEY"
{ "customerTokenKey": "tck_9f2c…", "tenantId": 14, "algorithm": "HS256", "header": "X-Tether-Customer-Token", "maxLifetimeSeconds": 3600 }
The key your server signs customer tokens with, so the agent knows which of your customers is asking. Shown once. Calling again rotates it, and tokens signed with the old key are refused at once. It is separate from the signing secret on your tool endpoint, so rotating one never breaks the other. Secret key only.
Keys
| Method and path | What it does |
|---|---|
GET /v1/account/keys | Every key by prefix, kind, label and dates. Never the key itself. |
POST /v1/account/keys | Issues a key. Body: { "kind": "secret" | "publishable" | "agent" | "inbox" | "ci", "label": "..." }. The key is shown once. An inbox key (tib_) is what the approval inbox runs on. A ci key (tci_) is what a repository's runner publishes with; add "allowReplace": true to let it replace knowledge documents already held (its kind is then ci_replace). See Repository. |
POST /v1/account/keys/{prefix}/rotate | Issues a replacement of the same kind and label, then revokes the old key. A provider key and a provider's inbox key are not rotated or revoked here (404 unknown_key); see Providers. |
DELETE /v1/account/keys/{prefix} | Revokes a key. It is refused on its next request. |
A tenant may hold at most 10 live keys (Account:MaxLiveKeys); over it,
409 key_limit_reached. The only live secret key cannot be revoked: 409 last_secret_key.
Rotate it instead.
curl -X POST "$TETHER_URL/v1/account/keys/tth_a1b2c3d4/rotate" \
-H "X-Api-Key: $TETHER_SECRET_KEY"
The response carries the new key and the revokedPrefix. Store the new key before you stop
using the old one; the old one stops working immediately.
Replace the signing secret
When the tool signing secret is lost or should change, and no key is in doubt:
curl -X POST "$TETHER_URL/v1/account/signing-secret/rotate" \
-H "X-Api-Key: $TETHER_SECRET_KEY"
The answer carries the new signingSecret, once. Every call and event is signed with it from
that moment, so set it on your tool endpoint and events webhook (TETHER_SECRET for the SDKs)
straight away. No key is revoked. If a key may have leaked too, use the call below instead.
Lost every secret key
If no secret key is left, get a new one by mail from the address that created the workspace: recovering a lost key.
A key leaked
One call that assumes the worst:
curl -X POST "$TETHER_URL/v1/account/compromise" \
-H "X-Api-Key: $TETHER_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "rotateSigningSecret": true }'
Every live key of every kind is revoked, one new secret key is issued, and with
rotateSigningSecret the secret that signs calls to your tool endpoint is replaced and returned.
If you had issued a customer-token key, it is replaced too and returned as customerTokenKey,
because whoever held the leaked key could have read it and gone on signing tokens as any of your
customers. Tokens signed with the old one are refused at once, so set the new key on your server
straight away.
Set the new signing secret on your endpoint at once: calls signed with the old one are refused.
Browser keys and agent credentials are revoked too, so reissue them from the new secret key.
What happened to the account
curl "$TETHER_URL/v1/account/events?limit=50" -H "X-Api-Key: $TETHER_SECRET_KEY"
{ "events": [{ "type": "recovery.redeemed", "at": "…", "keyPrefix": "tth_e5f6a7b8", "callerHash": "3fa1…", "detail": "1 secret key(s) revoked; signing secret rotated: no" }] }
Newest first, at most 200. Each change to the account leaves one event:
- keys:
key.issued,key.rotated,key.revoked; account.compromise;signing_secret.rotated;customer_token_key.issued;intent.changed;- recovery:
recovery.requestedandrecovery.redeemed.
keyPrefix names a key without revealing it. callerHash is a salted hash of the caller's
network address. It is set on recovery events, so a request you did not make stands out without
anyone's address being stored. Erasing the account removes its events with everything else.
Erase the account
curl -X DELETE "$TETHER_URL/v1/account" \
-H "X-Api-Key: $TETHER_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "confirm": "Acme Store" }'
confirm must equal the workspace name exactly, or the call answers 400 confirmation_required
and changes nothing. On success every row Tether holds for the tenant is hard-deleted: the
catalog, domain pack, upstreams, users and approval policy, keys, chat history, staged writes
and their approvals, executions and audit events, preferences, knowledge, usage records,
telemetry and feedback. The tenant row stays as an empty tombstone with no name, endpoint or
secrets, and a salted hash of the signup address, so that deleting an account cannot be used to
get another development tenant.
Errors
| Status | Code | Meaning |
|---|---|---|
400 | unknown_kind | kind is not secret, publishable, agent, inbox or ci |
400 | confirmation_required | confirm does not match the workspace name |
400 | invalid_intent | intent is not bring_your_ai, answers or answers_and_actions |
404 | unknown_key | No live key with that prefix on this account |
409 | key_limit_reached | Revoke a key you no longer use first |
409 | last_secret_key | That is the only live secret key |