Machine-readable copies of this site are published automatically at
/llms.txt and
/llms-full.txt, and every page is available as Markdown by appending .md to its
URL.Non-negotiable rules
Discover capabilities before assuming them
CallGET /v1/store at startup. It requires no scope, works with any valid key, and
returns the calling key’s own scopes plus the store’s enabled payment methods:
orders:read, orders:write, invoices:read, invoices:write,
products:read, products:write, webhooks:read, webhooks:write.
Error handling rules
The error body is
{ "error": "…" } everywhere except POST /v1/invoices/{id}/send,
which adds code and retryAfterSeconds. error is a human message, not a stable
machine code — branch on status and, where documented, on code.
Money formatting rules
Idempotency and retries
Always send anIdempotency-Key header on POST /v1/orders and POST /v1/invoices,
derived from your own purchase id. 1 to 255 characters.
A replayed key returns the original resource, whatever the new body says — Taberna
does not compare bodies. Keys never expire. Never generate a random key per HTTP attempt:
that defeats the entire purpose, because the retry of a timed-out create would mint a
second charge.
Webhook handler requirements
Delivery is at-least-once. A correct handler:Reads the raw body
Before any JSON parsing, for signature verification.
Verifies the HMAC in constant time
Reject non-matching requests with a non-2xx and stop.
Rejects stale timestamps
Taberna does not enforce a tolerance for you. Reject when
abs(now_seconds - X-Taberna-Timestamp) > 300 — a 5-minute window. Without this,
a captured request is replayable forever.Applies the change idempotently against business state
Never dedup on
X-Taberna-Delivery-Id alone. A manual resend re-queues the
same delivery row, so it arrives with the same delivery id as the original —
a handler that skips ids it has already seen silently drops every resend, which
is precisely the traffic a merchant is replaying to fix a problem.Guard on your own state instead: has this data.id (or your
extraContext.purchaseId / metadata.purchaseId) already been fulfilled? If
yes, no-op. Record the delivery id for correlation if you like, but do not let it
decide whether to act.Responds 2xx fast
Success means a
2xx within about 10 seconds. Queue the real work; a handler that
fulfils inline will time out under load and be replayed.Polling as a fallback, not a primary
Webhooks are the completion signal. When a webhook did not arrive, reconcile from your server against the authenticated endpoints —GET /v1/orders/{id} or
GET /v1/invoices/{id} — and act on the status.
Do not poll GET /v1/checkouts/{id} from your backend: it is the buyer-facing,
unauthenticated view and it omits extraContext and metadata, which is what you need
to correlate.
Stop polling as soon as the status leaves pending (orders) or open (invoices). Every
other state is terminal:
- Orders:
pending→completed|partial|expired - Invoices:
open→paid|partial|expired|voided
completed / paid only. Never fulfil on partial unless a human has
explicitly decided to honour underpayments — there is no automatic refund, and
completion uses a strict >= comparison with no tolerance.
Things that do not exist — do not invent them
An agent’s most common failure here is fabricating a plausible endpoint. None of the following exist:- A test or sandbox mode, or a
tbrn_test_key prefix. - A refund endpoint, or any way to return funds through the API.
- API routes to create or edit products, stores, invoice templates or API keys. All four are dashboard-only.
- A second webhook URL, or per-event webhook subscriptions.
- Currencies beyond
usdandeur. - Product types beyond
single-purchase. - Subscriptions, recurring billing or metered usage. Model these as repeated charges from your own scheduler.
- A
storeIdparameter on any/v1endpoint — the key carries the store.
/v1 surface is: /v1/orders, /v1/invoices, /v1/invoice-templates,
/v1/products, /v1/store, /v1/webhook-deliveries (all API-key authenticated) and
/v1/checkouts (unauthenticated, buyer-facing). If a route is not in that list, it does
not exist — check the API Reference tab rather than guessing.
Choosing orders versus invoices
When in doubt for a dynamic amount, use invoices — they need no product setup and carry
metadata for correlation.