Tether
DocsGitHub

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 pathWhat it does
GET /v1/account/confirmationWhether 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/confirmationSends 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 pathWhat it does
GET /v1/account/keysEvery key by prefix, kind, label and dates. Never the key itself.
POST /v1/account/keysIssues 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}/rotateIssues 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.requested and recovery.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

StatusCodeMeaning
400unknown_kindkind is not secret, publishable, agent, inbox or ci
400confirmation_requiredconfirm does not match the workspace name
400invalid_intentintent is not bring_your_ai, answers or answers_and_actions
404unknown_keyNo live key with that prefix on this account
409key_limit_reachedRevoke a key you no longer use first
409last_secret_keyThat is the only live secret key