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:
- Cache the
access_tokenwith its computed expiry (now + expires_inseconds). - Refresh early. Treat the token as expired about 60 seconds before its actual expiry so in-flight requests never race the deadline.
- Share in-flight refreshes. If multiple concurrent requests find the cache empty, they should await one token request, not fan out N of them.
- 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
- API scopes: what each scope grants and how the hierarchy works
- Errors and the response envelope: what 401 and 403 responses look like
- Payments API reference: the auth and OAuth endpoints in full