thru

API keys, workspaces and environments

The header

Every server-to-server request carries your key in a header:

css
x-api-key: thru_sk_<48 lowercase hex characters>

There is no Authorization: Bearer scheme.

ResponseMeaning
401 Authentication requiredNo key was sent.
401 Invalid API keyThe key is unknown or revoked, or the workspace is suspended. The response does not say which.

Creating and revoking keys

A signed-in person creates keys in the console, under Developers → API keys. The raw key is shown once. thru stores only a hash, so a lost key cannot be recovered; revoke it and create another.

EndpointWho can call it
GET /v1/api-keysAny key or console session. Lists keys without the secret.
POST /v1/api-keys { "name": "…" } (up to 80 characters)A console session only
DELETE /v1/api-keys/:idA console session only

An API key cannot create or revoke keys; those calls return 401 A dashboard session is required. This means a leaked key cannot mint itself a replacement or lock you out by revoking your other keys.

What a key can do

A key acts with the workspace's full authority, except for the actions reserved for a signed-in person: managing keys, members and roles, and deleting the workspace. A key can:

  • create payments, checkout sessions, products and invoices;
  • register and change webhook endpoints, and read their signing secrets;
  • read and rotate the checkout signing secret;
  • issue refunds;
  • add and change settlement addresses.

Treat a key like the password to your revenue. Keep it on your server, and never put it in a browser, a mobile app or a public repository.

Testnet and mainnet

There are no separate test and live keys. One key, one base URL and one set of webhook endpoints serve both networks. The network is a property of each payment:

ObjectHow its network is chosen
Payment (POST /v1/payments)The network field. If you leave it out, the server's default applies (mainnet on api.thru.la). Always send it.
ProductThe network field. It defaults to mainnet, and every checkout of the product uses it.
InvoiceThe network field. It defaults to mainnet.
Checkout sessionCopied from its product or invoice. You cannot set it on the session.

Every payment and session carries network, and so do the events you fulfil from: payment.confirmed, payment.underpaid, payment.overpaid and payment.refunded (in data.payment.network), payment.expired (data.network), every checkout.session.* event, and settlement.completed / settlement.failed. A session also carries livemode (true exactly when network is mainnet). Your fulfilment code must check it. Otherwise a payment made with free testnet tokens could unlock something you sell for real money.

Use a separate workspace for staging

Because keys are not tied to a network, the way to keep staging away from production money is a second workspace, created in the console. It has its own keys, webhook endpoints, return origins, checkout secret and settlement addresses. Point your staging deployment at that workspace's key.

Endpoints that need no key

These are called by thru's hosted pages and by the browser SDKs. The id in the path is the credential, and the responses never include your reference or metadata.

EndpointUsed by
GET /v1/public/payments/:idYour own payment screen, or @thru-payment/checkout-core
GET /v1/public/checkout/sessions/:id, POST /v1/public/checkout/sessions/:id/redeem, POST /v1/public/checkout/sessions/:id/cancelThe hosted page at /c/:id. Limited to 120 requests per minute per checkout session per IP.
GET /v1/public/products/:slug, POST /v1/public/products/:slug/checkoutThe hosted payment link at /pay/:slug
GET /v1/public/invoices/:publicId, POST /v1/public/invoices/:publicId/payThe hosted invoice at /i/:publicId
GET /v1/docs, GET /v1/docs/:topicThese docs

Do not grant anything based on a public endpoint's answer. A browser can be made to show anything, so grant from your webhook handler or from an authenticated read.