Skip to main content
Every /v1 merchant endpoint authenticates with a store API key sent as a bearer token:
The buyer-facing /v1/checkouts endpoints are the exception: they are deliberately unauthenticated, because they run in the buyer’s browser. Never ship an API key to a page.

Key format

A live key is always tbrn_live_ followed by 48 hexadecimal characters. That is the only accepted shape — anything else fails validation before it reaches the store lookup. The tbrn_live_ prefix exists so that a key leaked into a log, a screenshot or a public repository is recognisable both to a human and to automated secret scanners.
A key is bound to one store. Taberna derives the store from the key, which is why no /v1 endpoint takes a store id — POST /v1/orders creates an order for the key’s store and nothing else.

Scopes

Scopes are chosen when the key is minted and are fixed for its lifetime. To change them, create a new key and revoke the old one. Notes worth internalising:
  • Invoice templates read under invoices:read, not a scope of their own — they are invoice authoring material.
  • webhooks:write is replay only. It lets a key push a failed delivery back into the queue. It does not permit changing where a store’s events are sent; that stays a dashboard action.
  • Choosing scopes is mandatory. There is no implicit full-access key. An empty scope array, or an unknown scope name, is rejected at key creation.
Grant the minimum. A server that only sells one thing needs orders:write and orders:read — nothing else. Every scope you add is blast radius if the key leaks.

The one endpoint that needs no scope

GET /v1/store — “whoami” — is callable by any valid key, whatever it was granted. Its response includes the calling key’s own scopes array:
This exists so an integration can discover what it may do without provoking a 403 to find out. Call it at boot, assert the scopes you need, and fail your own startup with a clear message rather than failing a customer’s checkout later. storefrontUrl is the canonical public origin of the store’s storefront (an active custom domain wins over the slug subdomain) and is null while the storefront is unclaimed or switched off.

401 versus 403

The two are not interchangeable and the distinction matters for how your client should react.
Returned for every authentication failure: missing header, malformed header, unknown key, revoked key, expired key.
The body is always exactly { "error": "Unauthorized" }. The reason is deliberately not disclosed — telling a caller “revoked” rather than “unknown” would confirm to whoever is guessing that a key exists. Never branch on the body.The RFC 6750 challenge in the WWW-Authenticate header is the only signal that distinguishes cases:

Rotation and revocation

Keys are minted, listed and revoked from the dashboard by a store owner.
1

The raw key is shown once

At creation, and only at creation. Taberna keeps a SHA-256 hash and cannot recover the original. Put it straight into your secret manager.
2

Rotate by overlap, not by swap

Mint the new key first, deploy it, confirm traffic is flowing on it, then revoke the old one. Because scopes are immutable, a scope change is always a rotation.
3

Revocation is immediate

A revoked key answers 401 on the next request. So does a key past its optional expiresAt. Keys created without an expiry do not expire.
A key’s lastUsedAt is tracked, so the key list in the dashboard will tell you whether a key you are about to revoke is still receiving traffic.

Handling auth in your client