Tether
DocsGitHub

API reference

Authentication

One header authenticates every request. Three optional headers narrow it. Nothing in a request body ever changes who is asking.

The key

curl https://api.trytether.ge/v1/tools \
  -H "X-Api-Key: tth_9f3c…"

Authorization: Bearer tth_9f3c… is accepted as an equivalent — some clients and gateways make the custom header awkward. Both resolve to the same tenant.

Every path except /health, /swagger and /openapi requires a key.

Kinds of key

What a credential may reach depends on which kind it is, and the prefix says which.

PrefixKindReaches
tth_SecretThe whole tenant API. Server-side only
tag_AgentPOST /v1/chat, POST /v1/chat/stream, POST /mcp. Nothing else
tpk_PublishablePOST /v1/chat and POST /v1/chat/stream. Safe to ship in a page
tac_Action tokenConfirms the single staged write it was minted with
tpv_Provider/v1/provider: creating and managing the businesses a software provider serves, and /v1/user-sessions. See Providers
tib_InboxAsking Tether to mail a sign-in link, spending it, and opening and closing user sessions; with a session, the approval routes as that person. Nothing else (error.reason not_an_inbox_route, or user_session_required without a session)
tsb_Preview sandboxIssued with no other credential by POST /v1/preview/sandbox: its own GET /v1/preview/sandbox, POST /v1/chat and POST /v1/chat/stream with the preview-only assistant, and POST /v1/preview/sandbox/claim, which revokes it. Nothing else (error.reason not_a_sandbox_route). See Preview Mode
tci_CIWhat tether publish --send sends from a repository's own runner: the import's draft step, knowledge documents with a repository source, a vocabulary suggestion, and reading the pack, the knowledge list and itself. It never changes the live pack, policy, catalog or a key. See Repository (error.reason not_a_ci_route, ci_draft_only, ci_source_required or ci_replace_not_allowed)

An agent key is the credential your assistant runs with. It stages writes and reads your catalog; it cannot register a capability, change your approval policy, point the tool endpoint somewhere else, or issue credentials. That separation is the point: an agent that can rewrite the declaration governing it is not governed by it, so registering capabilities is something your secret key does during onboarding and your agent never does at all.

Anything not named above is refused for every kind but the secret key, so a route added later is closed to a browser or an agent credential until someone opens it deliberately. /v1/provider is the one exception the other way: no secret key reaches it, only a provider key, which Tether issues when it grants a platform provider authority.

How keys are stored

A key is tth_ followed by 48 hex characters. Tether stores only its SHA-256 hash and a 12-character non-secret prefix, so:

  • The plaintext is shown once, at issuance. There is no way to read it back.
  • A database disclosure does not hand anyone the ability to spend your budget.
  • The prefix lets a key be identified in a list without revealing it.

You issue, rotate and revoke your own keys, and recover from a leaked one in a single call, with your secret key: see Account. Only plan changes and keys for a tenant other than your own are operator actions.

Failure modes

Both of these return 401 with an identical shape:

{ "error": { "code": "missing_api_key", "message": "Provide an API key in the X-Api-Key header." } }
{ "error": { "code": "invalid_api_key", "message": "The API key is not valid." } }

An unknown key and a revoked key are deliberately indistinguishable. Telling a caller which half of a guess was right is a free hint.

The scope headers

HeaderTypeMeaning
X-Tether-Scope-Idpositive integerA narrower boundary inside the tenant — a site, ledger, region, workspace
X-Tether-User-Idpositive integerThe individual acting. Attributed on staged writes and memory
X-Tether-User-RolestringPassed through to your endpoint and your permission checks. Defaults to api
X-Tether-User-Sessiontus_…A user session: acts for the user Tether signed in, in their business, on the approval routes only. The user headers are not read with it
X-Client-Surfaceweb | mobileWhich of your surfaces is asking. Affects retrieval and telemetry only

All four are optional. Values that are not positive integers are treated as absent rather than rejected, so a stray X-Tether-Scope-Id: null scopes the turn to the whole tenant instead of failing loudly.

Only a secret key may set them. They are read from a tth_ request and ignored on every other kind. An agent key and a publishable key speak for the tenant and for nobody in particular, and an action token speaks for the approver it was minted with — see below.

curl -X POST https://api.trytether.ge/v1/chat \
  -H "X-Api-Key: $TETHER_SECRET_KEY" \
  -H "X-Tether-Scope-Id: 9" \
  -H "X-Tether-User-Id: 431" \
  -H "X-Tether-User-Role: shift_lead" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "what is still open?" }'

Authenticated versus asserted

Approving with an action token

An action token proves which write may be confirmed. On its own it names nobody, so under any registered approval policy it confirms nothing, and where no policy is registered a confirm that names nobody is refused rather than executed.

To let a token approve, bind the approver when you stage the write:

curl -X POST https://api.trytether.ge/v1/actions \
  -H "X-Api-Key: $TETHER_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tool": "refund_order", "arguments": { "id": 4412 }, "approverUserId": 431 }'

The returned confirmation_token then speaks for user 431 and for no one else. The identity travels with the token, in Tether's database, so whoever holds the token cannot choose a different approver — and cannot satisfy a two-person policy alone by naming two.

Bind the approver only after your own system has authenticated them. Tether takes the same view of approverUserId as of X-Tether-User-Id: your backend vouched for it.

Tokens that travel with a turn

Two more headers may come with a request from any kind of key. Neither authenticates the caller to Tether, and neither widens what the key may reach.

HeaderWhat Tether does with it
X-Tether-Customer-TokenVerifies it against your customer-token key and passes the customer to your API. Refused 401 invalid_customer_token when it does not verify. See Verified customers
X-Tether-Upstream-TokenChecks its shape only, and sends it to your imported API as Authorization: Bearer … when your userToken mode says so. Never stored or logged. Refused 400 invalid_upstream_token when malformed. See Calling your API as the signed-in user

Never call Tether from a browser

The API key is a bearer credential for your whole tenant: it can read your catalog, replace it, stage writes, and spend your budget. Anything holding it can do all of that.

The intended topology is one hop:

browser ──▶ your backend ──▶ Tether
             (holds the key, sets scope and user from the session)

Scope never comes from the body

A request body carrying organizationId or locationId is accepted and those fields are ignored. They are legacy and they are not read. See Tenants and scope.

The signing secret is a different credential

Your tenant also has a signing secret, and it is not the API key:

DirectionPurpose
API keyYou → TetherProves which tenant is calling
Signing secretTether → youProves a tool call really came from Tether

Mixing them up is the most common cause of a signature mismatch. See Verifying signatures.