Skip to main content

Authentication

Every call carries a bearer token in the Authorization header. There are two kinds: API keys for integrations, and session tokens (JWTs) for the three kinds of people who sign in. The API also needs to know which store a storefront call is for.

API keys — for integrations​

An API key is the right credential for anything that is not a person: an ERP job, a supplier feeding data, a reporting script.

  1. A staff member creates the key under Settings → API keys (or POST /admin/api-keys) with a name, a set of scopes and an optional expiry. Scopes are permission strings such as order.view, invoice.view or import.ingest, and a key can never carry a scope its creator lacks.
  2. The secret starts with tgk_ and is shown once. Store it in a secrets manager.
  3. Send it as a bearer token:
curl https://api.example.com/admin/orders \
-H "Authorization: Bearer tgk_…"

A key acts with admin audience for the organization that created it, limited to its scopes. Last used is recorded on every call. Rotate issues a new secret and invalidates the old one immediately; Revoke stops the key for good.

Least privilege

Give a supplier's key only import.ingest; give a reporting job only the *.view scopes it reads. A key with import.ingest can discover import targets and templates but can do nothing else.

Session tokens — for people​

The apps sign in with email and password and receive a JWT bound to one audience. Tokens are not interchangeable: a buyer token on an admin route is a 403 with "wrong token audience".

AudienceWhoSign inLifetime
adminSeller-side staffPOST /admin/auth/loginJWT_TTL, default 24 h
storefrontBuyers (users of a customer company)POST /storefront/auth/login30 days, sliding — the storefront calls POST /storefront/auth/refresh on boot
vendorMarketplace vendor usersPOST /vendor/auth/loginJWT_TTL, default 24 h
curl -X POST https://api.example.com/admin/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"…"}'
# → {"token":"<JWT>"}

If an email exists in more than one organization on the platform, the login answers "this email exists in multiple organizations; pass org_id"; retry with "org_id": <number> in the body. Login routes are rate-limited (10 attempts per minute per address by default) and answer 429 with "too many requests, slow down".

Staff tokens embed the resolved permission set; every admin route checks a permission (order.view, order.manage, …) and hides itself from callers who lack it. Buyer tokens carry the customer company; buyer routes authorize on ownership. Single sign-on (OIDC, SAML) issues the same tokens — see the admin guide's SSO providers.

Which store is this call for?​

One API serves every tenant. Staff and vendor tokens carry their organization, so admin and vendor routes need nothing more. Storefront routes are bound to a website by host: the API reads X-Tenant-Host, then X-Forwarded-Host, then the request Host, and matches it against the website's domain or a verified custom domain. There is no fallback — an unknown host is refused.

So when you call storefront endpoints from a server rather than a browser on the store's own domain, name the store:

curl https://api.example.com/storefront/products \
-H "X-Tenant-Host: shop.acme-industrial.com"

Guest browsing (catalog, search, product pages) needs no token; carts, checkout, quotes, orders and account pages need a storefront token.

Capability URLs​

A few assets are served by signed, time-limited URLs instead of a bearer token, so a browser or an email link can open them directly: invoice PDFs and media renditions. The signature covers the path and expiry; a guessed or expired URL is rejected. Treat these URLs as secrets while they are valid.

Errors​

StatusMeaning
401Missing, malformed, expired or revoked token
403Wrong audience, or a permission or scope the token lacks
404Also returned for records that exist but belong to another organization or account
409Duplicate — the same payment reference, PO number or idempotency key again
422Valid request that the business rules refuse (insufficient stock, over budget, unknown SKU)
429Rate limited

Every error body is { "code": "...", "message": "..." }.