cartwright
API reference

API keys

Mint, store, verify and revoke Bearer API keys for the REST and MCP tool surfaces — HMAC-peppered storage, scope-gated, audited.

Cartwright exposes its full operational tool surface (catalog, orders, discounts, pages, campaigns, …) over two authenticated programmatic interfaces:

  • RESTPOST /api/v1/tools/<name> (one tool per call).
  • MCPPOST /api/mcp (Model Context Protocol; see MCP server).

Both authenticate the same way: a Bearer API key in the Authorization header. Source: lib/api-auth.ts, lib/scopes.ts.

Key format

sb_live_<24 random bytes, base64url>

The sb_live_ prefix makes keys greppable in logs and recognizable in the admin UI; entropy is randomBytes(24) (192 bits). Keys are created in /admin/api-keys (shop-owner only). The plaintext is shown exactly once at creation — only the hash is persisted.

Storage — the database never holds a usable key

Cartwright stores a keyed hash, not the key:

keyHash = HMAC-SHA256(key_plaintext, pepper = AUTH_SECRET)

A database leak alone cannot forge a key: the HMAC pepper is AUTH_SECRET, which lives only in the server environment, never in the DB. Plain SHA-256 was rejected for exactly this reason. AUTH_SECRET is reused deliberately — it's already required by Auth.js, so there's no new secret to rotate.

Verification flow

authenticateApiKey(req) and requireApiScope(req, scope) are the entry points every REST/MCP route calls first:

  1. Extract the bearer token — missing/malformed → 401.
  2. Prefix check — must start with sb_live_ (cheap reject before any DB hit).
  3. Hash + lookup — HMAC the token, look up ApiKey.keyHash (unique index). No row → 401.
  4. RevocationrevokedAt set → 401 API key revoked.
  5. Last-used stamplastUsedAt updated fire-and-forget; never fails the request.
  6. Scopes — the key's scopes JSON is parsed; malformed → 401.

The function never throws — it returns { actor } or { error }. requireApiScope then checks the key carries the tool's required scope (hasScope); missing → 403 naming the scope. See Scopes & tools.

Audit identity & revocation

Each authenticated actor is apikey:<id> in the audit log (distinct from user:<id> and storefront-chat:<sid>). Revoke by setting revokedAt via /admin/api-keys; verification rejects revoked keys immediately.

AUTH_SECRET is mandatory — getKeyPepper() throws if it's missing. Rotating it invalidates all existing API keys (and sessions).

On this page