TBBNMerchantDocs
API

Authentication

Merchant API keys

Authorization: Bearer tbbn_sk_live_... for live, tbbn_sk_sandbox_... for test mode — the prefix determines the mode, not the host you call. A test key works only on your Merchant's test account, so nothing it does reaches live data; see Test mode. Create keys in your Merchant Dashboard; you'll see the full value exactly once, so store it like any other secret. Keys are independently revocable — rotating one doesn't affect any human session on the same account.

A key the dashboard creates can call the whole API. A key can instead be limited with scopes: a list of API areas, the first part of the path after /v1/ (listings, offers, trade-sessions…). A request outside them gets 403 SCOPE_NOT_GRANTED.

Check a key works with GET /v1/auth/me — any key may call it, whatever its scopes. It answers with the Merchant the key belongs to and whether it's a test key:

{
  "principalType": "merchant_api_key",
  "merchantId": "6f1c…",
  "merchant": {
    "id": "6f1c…",
    "displayName": "Your store",
    "status": "APPROVED",
    "livemode": true
  },
  "livemode": true,
  "restriction": null
}

New keys start tbbn_sk_. Keys from before November 5, 2026 start sk_live_ or sk_sandbox_ and keep working, so don't check for a particular prefix in your own code.

A key found in public is revoked. TBBN watches for new keys in public code with GitHub. A real one found there is revoked straight away, and you get an email and the api_key.exposed webhook.

Limit a key when you create it or with PATCH /v1/api-keys/{id}:

  • expiresAt: after this time the key answers 401 API_KEY_EXPIRED.
  • allowedIps: the IP addresses or CIDR ranges it works from; from anywhere else it answers 403 API_KEY_IP_NOT_ALLOWED.

A 401 means the key is wrong, revoked or rotated. restriction: "WIND_DOWN" means your production access is paused (an unpaid plan or a usage balance below zero): you can still finish or unwind trades under way, but not start new activity.

Merchant representatives and step-up verification

People on your team sign in to the Merchant Dashboard with a one-time code and get a session access token. Changing your merchant's own settings — PATCH /v1/merchants/{id}, for example checkoutEnabled, matchingEnabled or counterpartyHoldDays — needs that session, from a representative with the OWNER or ADMIN role. An API key is refused. Once your merchant is approved, the change also needs a step-up token:

  1. POST /v1/auth/step-up/request with the session — a verification code goes to the representative's email.
  2. POST /v1/auth/step-up/confirm with { "code": "123456", "scope": "merchant:update" } — returns a token valid for 5 minutes, for this kind of change only.
  3. Send the change with both headers:
PATCH /v1/merchants/{id}
Authorization: Bearer <session access token>
X-TBBN-Step-Up-Token: <step-up token>
Content-Type: application/json

{ "checkoutEnabled": false }

The dashboard's settings page runs these steps for you.

The same check, with "scope": "credentials:manage", applies to creating, rotating or limiting a live API key, rotating an OAuth client secret, adding a webhook or rotating its secret from a session on a live account, and opening a finished Stripe payout account. Without the token the answer is 403 STEP_UP_REQUIRED, with the scope to confirm in details.scope. A token lasts 5 minutes and can be used for several of these.

Seller sign-in (for embedded widgets or your own UI)

Sellers never have a password. Sign-in is a one-time code sent by email, or optionally phone, or a magic link — issued by TBBN, not something you implement yourself if you're using an official widget or SDK flow. If you're building a fully custom experience against the raw API: the code is 6 digits, expires in 10 minutes, and is capped at 5 send-requests per hour per address — build your UI to handle that rate limit gracefully (a clear "too many attempts, try again in a bit" state) rather than retrying silently.

Acting on behalf of a seller

Any route that mutates one seller's state (accept/reject/cancel an offer, cancel a trade session, confirm a peer-to-peer exchange) needs to know which seller you're acting for. There are exactly two valid ways:

  • The seller themself, authenticated with their own session — they can only ever act for themselves.
  • A merchant the seller is linked to — the core integration case, where your platform drives the flow through the API on behalf of your own seller. This is also the only way to act for a headless seller (one with no linked TBBN account at all).

An actingSellerId field on a request body is a statement of intent, not a grant of identity — it still has to match one of the two cases above. Naming a seller you don't actually have a relationship to gets rejected, not honored.

Rate limits

Every response carries the standard RateLimit and RateLimit-Policy headers. See API conventions.