Authentication

Bearer tokens, scopes, live vs test mode, and key rotation.

Overview

Every Partner API call authenticates with a bearer token in the Authorization header. Tokens are partner-scoped, prefixed by mode (live or test), and optionally restricted to a subset of endpoints via scopes. There is no OAuth, no token refresh, no session state.

Bearer token

Send your API key in the Authorization header on every request:

Authorization: Bearer jo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Missing, malformed, unknown, or revoked keys return 401 unauthorized. Keys are validated per-request against the partner's key table; revoking a key takes effect on the next request, no cache warmup required.

Keys are secrets. Never ship them in client-side JavaScript, mobile apps, or git. If you accidentally expose a key, revoke it in the partner portal (/portal/api-keys) and generate a new one immediately.

Base URL

Call the API at one of two hosts. Both serve identical routes and data:

  • https://<partner-slug>.jopay.app — most partners call from their own subdomain so branding context is preserved end-to-end
  • https://admin.jopay.app — available for partners without a subdomain

Scopes (restricted keys)

By default a new key can call every v1 endpoint. You can restrict a key to a subset of endpoints by selecting scopes when you create it. Use restricted keys for CI, read-only dashboards, or integrations that should only touch a subset of resources.

ScopeEndpoints
requests:readGET /api/v1/requests, GET /api/v1/requests/:id
requests:writePOST /api/v1/requests/create, POST /api/v1/requests/:id/cancel
merchants:readGET /api/v1/merchants, GET /api/v1/merchants/:id
merchants:writePOST /api/v1/merchants

Handling 403 insufficient_scope

Calling an endpoint the key isn't scoped for returns 403 insufficient_scope. The response body tells you exactly which scope is missing:

{
  "ok": false,
  "error": "insufficient_scope",
  "required_scope": "requests:write"
}

Add the missing scope to the existing key in the partner portal, or generate a new key with broader scope. Restricted keys are not upgraded by JoPay — you always swap keys, never mutate a deployed key's scope silently.

Live and test mode

Every key is exactly one of two modes. The prefix is authoritative:

  • jo_live_… — hits production data. Every request and merchant created by a live key is real.
  • jo_test_… — hits sandbox data, fully isolated from live. Test-mode requests are tagged test_mode: true in every response, filtered out of live views, and never visible to live keys.

A live key and a test key behave identically at the auth layer; the difference is the data they see. Use test keys in CI, staging, and integration tests so a buggy deploy can't accidentally create real payment requests.

You cannot mix modes in one call: a test key used against a live merchant (or vice versa) returns 400 merchant_mode_mismatch.

Most partners keep separate keys per environment: a jo_test_ key for CI and staging, a jo_live_ key for production. Loading the wrong key into the wrong environment then fails at the auth layer with a clear error, not silently.

Rotating keys

Rotation is a two-step swap, not an in-place mutation:

  1. Create a new key in the partner portal with the same scopes as the old one.
  2. Deploy the new key to your environment. The old key continues to work until you revoke it.
  3. Once the new key is live and handling traffic, revoke the old one.

There is no "rotate in place" endpoint. Every key has a stable identity from creation to revocation; this makes audit trails and incident forensics unambiguous.

Other auth types (for reference)

The Partner API (/api/v1/*) is the only surface partner-side code touches. For completeness, JoPay uses three other authentication models internally:

Auth typeUsed by
Admin sessionCookie-based session for the admin.jopay.app panel. JoPay staff only.
Merchant sessionCookie-based session for the merchant dashboard. Merchants authenticate via Sequence; the session cookie is set server-side.
Cron secretShared secret in the Authorization header for scheduled-job endpoints. Not callable from partner or merchant contexts.

These are listed only so you recognize them if they appear in logs or error messages. Your code never sends or receives them.