Guides

Authentication

API key, OAuth 2.0 client credentials, and JWT authentication for the Coinbax Payments and Workspace APIs.

Last updated 2026-07-16View as Markdown

Coinbax has two API surfaces with different authentication:

API Base URL (staging) Auth
Payments API https://api-staging.coinbax.com/api/v1 X-API-Key header, or OAuth 2.0 Bearer token
Workspace API https://core-staging.coinbax.com/api/v1 Authorization: Bearer <JWT>

Production bases are https://api.coinbax.com/api/v1 and https://core.coinbax.com/api/v1. Staging credentials only work against staging; the environments share nothing.

API keys (Payments API)

The simplest way to call the Payments API from a backend:

curl https://api-staging.coinbax.com/api/v1/transactions \
  -H "X-API-Key: $COINBAX_API_KEY"

Key facts:

  • Shown once. The key is returned a single time at creation and cannot be retrieved later. Store it in a secret manager immediately.
  • Scoped. Every key carries a set of scopes that bound what it can do. New keys default to read-only scopes; grant write scopes deliberately.
  • Server-side only. Never ship an API key in a browser, mobile app, or public repository. If a key leaks, regenerate it via POST /auth/api-key/regenerate.

OAuth 2.0 client credentials (service-to-service)

For service-to-service integrations, and for any client where you want short-lived credentials instead of a long-lived key, use the OAuth 2.0 client_credentials grant. You exchange a client ID and secret for a Bearer token, then send that token on Payments API requests.

Create an OAuth client

OAuth clients are created with an admin-scoped API key:

curl -X POST https://api-staging.coinbax.com/api/v1/oauth/clients \
  -H "X-API-Key: $COINBAX_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Backend Service",
    "scopes": ["read:transactions", "write:transactions"],
    "grantTypes": ["client_credentials"]
  }'

The response includes clientId and clientSecret. Like API keys, the secret is shown once.

Exchange credentials for a token

curl -X POST https://api-staging.coinbax.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "coinbax_client_...",
    "client_secret": "coinbax_secret_...",
    "scope": "read:transactions write:transactions"
  }'

scope is optional and space-separated; omit it to receive all scopes granted to the client. The token arrives in the standard response envelope:

{
  "success": true,
  "data": {
    "access_token": "eyJhbGciOi...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "read:transactions write:transactions"
  },
  "meta": { "timestamp": "...", "requestId": "..." },
  "error": null
}

Then call the API with the token:

curl https://api-staging.coinbax.com/api/v1/transactions \
  -H "Authorization: Bearer eyJhbGciOi..."

Token lifecycle: cache, refresh, retry

Do not request a new token per API call. The correct pattern:

  1. Cache the access_token with its computed expiry (now + expires_in seconds).
  2. Refresh early. Treat the token as expired about 60 seconds before its actual expiry so in-flight requests never race the deadline.
  3. Share in-flight refreshes. If multiple concurrent requests find the cache empty, they should await one token request, not fan out N of them.
  4. Retry on 401. If a request returns 401 with a token you believed valid (revocation, clock skew), discard the cached token, fetch a fresh one, and retry the request once.
let cached = null; // { token, expiresAt }
let inflight = null;

async function getAccessToken() {
  if (cached && Date.now() < cached.expiresAt - 60_000) return cached.token;
  inflight ??= fetchToken().finally(() => { inflight = null; });
  cached = await inflight;
  return cached.token;
}

async function fetchToken() {
  const res = await fetch(`${BASE}/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "client_credentials",
      client_id: process.env.COINBAX_CLIENT_ID,
      client_secret: process.env.COINBAX_CLIENT_SECRET,
    }),
  });
  const { data } = await res.json();
  return { token: data.access_token, expiresAt: Date.now() + data.expires_in * 1000 };
}

Tokens can be revoked with POST /oauth/revoke, and all tokens for a client with POST /oauth/clients/{clientId}/revoke-all-tokens.

JWT (Workspace API)

The Workspace API authenticates humans and dashboard-style integrations with JWTs:

# Log in to obtain tokens
curl -X POST https://core-staging.coinbax.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@example.com", "password": "..." }'

# Use the access token
curl https://core-staging.coinbax.com/api/v1/workspaces/me \
  -H "Authorization: Bearer <access-token>"

Access tokens expire after one hour. Use POST /auth/refresh to obtain a new access token without re-authenticating, and POST /auth/logout to invalidate the session.

Choosing a method

You are building Use
A backend that creates payments API key, or OAuth for short-lived credentials
A service-to-service integration OAuth 2.0 client credentials
A tool against workspace data (customers, settings, webhooks) Workspace API with JWT
Anything in a browser or mobile app OAuth via your own backend proxy; never embed keys or secrets client-side

Next steps