cheapkeys

Authentication

Authenticate every request with a supplier-scoped bearer token. One authentication scheme covers everything — send an Authorization header:

curl "https://sandbox-pay.mmokick.com/api/v1/whoami" \
  -H "Authorization: Bearer $CK_TEST_TOKEN" \
  -H "Accept: application/json"

Expected 200 response:

{
  "token": {
    "id": "01JZV9AB2CD4EF6GH8JK0MN2PQ",
    "name": "repricer-bot",
    "mode": "test",
    "scopes": ["offers:read", "offers:write", "pricing:read"],
    "created_at": "2026-06-02T09:14:00Z",
    "last_used_at": "2026-07-11T13:59:41Z"
  },
  "supplier": {
    "id": "01JZV8K2E4Q6S8A0C2E4G6J8KM",
    "name": "Keyforge Ltd",
    "status": "active"
  },
  "environment": "sandbox",
  "rate_limits": { "reads": 600, "writes": 120, "sync": 300, "bulk": 12, "simulate": 120, "test": 6 }
}

GET /whoami is your integration smoke test: it confirms the token is valid, tells you which scopes it carries, and reports the environment it belongs to. Use it first in any new integration.

Get a token

Create tokens yourself in the supplier portal, under Settings → API tokens → Create (route supplier.developers.tokens). Two-factor authentication is required to create a token. A token is shown once at creation and hashed at rest; if you lose it, revoke it and create another.

  • Sandbox access is instant and auto-approved: submitting the supplier application in the portal approves your supplier into the sandbox on the spot, so you can create a ck_test_ token in the portal (env toggle = Sandbox) — or use the published seeded token — and call the sandbox base URL within minutes. One portal manages both your Live and Sandbox tokens, so the same Settings → API tokens flow works for either. See Environments.
  • You may hold up to 25 tokens per environment. Tokens stay valid until you revoke them, and you can revoke any token instantly in the portal. last_used_at is tracked and displayed so you can spot and retire stale tokens.

Export your token once and reuse it across every example in these guides:

export CK_TEST_TOKEN="ck_test_..."

The header

Authorization: Bearer <token>

Tokens carry a prefix that names the environment:

Prefix Environment Base URL
ck_test_ Sandbox https://sandbox-pay.mmokick.com/api/v1
ck_live_ Production https://pay.mmokick.com/api/v1

A token is the prefix plus 40 random base62 characters, for example ck_live_1u2P9zXqK7mA4dT8rW3yB6nC0eF5gH2jL9sV4xQ7. Use tokens for server-to-server calls: the API sends no CORS headers, so browser use is unsupported by design. Keep tokens out of client-side code, URLs, and logs.

Match the token prefix to the host. Using a ck_test_ token against production (or a ck_live_ token against sandbox) returns 401 with code wrong_environment — the request always fails loudly rather than falling back silently.

Scopes

Scopes are assigned at token creation; the default is all scopes. A token missing the scope an endpoint requires gets 403 with code insufficient_scope.

Scope Grants
catalog:read Products, categories
pricing:read Fee schedule, pricing simulation
offers:read / offers:write Offers read / create, update, activate, price, stock
inventory:read / inventory:write Inventory list / upload, invalidate
orders:read Orders read model
balance:read Balance and ledger
webhooks:manage Webhook endpoints, deliveries log, test-fire, resend

Create a token with only the scopes an integration needs. A repricer bot, for example, needs offers:write and pricing:read and nothing else.

Failure responses

Every authentication and authorization failure is an RFC 9457 problem+json body with a stable machine code you switch on. Codes stay stable; title and detail text may change.

401 — the token is the problem. You are not (yet) authenticated:

{
  "type": "https://pay.mmokick.com/developers/errors#invalid_token",
  "title": "Invalid token",
  "status": 401,
  "code": "invalid_token",
  "detail": "The bearer token is unknown.",
  "trace_id": "req_9f3b2c61a8d44e0f"
}

The 401 codes and what to do:

code Meaning What to do
unauthenticated The Authorization header is missing or malformed Send Authorization: Bearer <token>
invalid_token The token is unknown Check the token; create a new one in the portal
token_revoked The token was revoked in the portal Issue a new token
wrong_environment A ck_test_ token was used on production, or ck_live_ on sandbox Use the host that matches the prefix

403 — the token is valid, but not allowed here. Either it lacks a scope or the supplier account is suspended:

{
  "type": "https://pay.mmokick.com/developers/errors#insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "This token is missing the required scope: offers:write.",
  "trace_id": "req_1a2b3c4d5e6f7081"
}
code Meaning What to do
insufficient_scope The token lacks a required scope Issue a token that carries the scope
supplier_suspended The supplier account is suspended by staff Contact support; GET /whoami still works

A request for another supplier's resource returns 404 with code not_found (rather than 403), keeping the existence of resources you cannot access private.

Token hygiene

  • Rotate by creating a new token and revoking the old one — create-then-revoke is the rotation for API tokens.
  • Revoke immediately if a token is exposed. Revocation takes effect at once; the next call with a revoked token gets 401 / token_revoked.
  • Tokens stay out of webhook payloads and every response body. If you see a token in a payload, it did not come from us.
  • Quote the X-Request-Id header (echoed in error bodies as trace_id) in any support request — it lets us find the exact call in our logs.

Next steps