Authentication
The WalletPlatform API uses the OAuth 2.0 Client Credentials flow.
Every request (except /auth/token and /.well-known/*)
requires a Bearer JWT.
Get a Token
POST
/api/v1/auth/token
Request body
| Field | Type | Description | |
|---|---|---|---|
| tenant_id | UUID | required | Your tenant identifier. |
| application_id | UUID | required | The application requesting the token. Must belong to the tenant and be Active. |
| application_secret | string | required | Plaintext secret. Server-side verification is Argon2id with per-row salt. |
| grant_type | string | required | Must be exactly client_credentials. |
Response
โ 200 OK
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3599,
"expires_at": 1747654800,
"scope": "passes:read passes:write templates:read messages:read messages:write webhooks:write analytics:read"
}
Scopes
The scopes embedded in the JWT depend on the application's AppType:
| Scope | Grants |
|---|---|
| passes:read | GET /passes, /passes/{id}, /passes/{id}/versions |
| passes:write | POST /passes, PATCH /passes/{id}, DELETE /passes/{id}, /batches |
| templates:read | GET /templates* |
| templates:write | (reserved for future write endpoints) |
| messages:read | GET /messages*, /passes/{id}/messages |
| messages:write | POST /messages |
| webhooks:write | All /webhooks endpoints |
| analytics:read | GET /analytics/events, /analytics/summary |
Using the token
Attach the JWT as a Bearer header on every request:
curl https://api.walletplatform.io/api/v1/passes \
-H "Authorization: Bearer $TOKEN"client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var passes = await client.GetFromJsonAsync<JsonElement>(
"https://api.walletplatform.io/api/v1/passes");const r = await fetch('https://api.walletplatform.io/api/v1/passes', {
headers: { 'Authorization': `Bearer ${token}` }
});r = requests.get(
'https://api.walletplatform.io/api/v1/passes',
headers={'Authorization': f'Bearer {token}'},
)$ctx = stream_context_create(['http' => [
'header' => "Authorization: Bearer $token",
]]);
$body = file_get_contents('https://api.walletplatform.io/api/v1/passes', false, $ctx);Token lifetime
Tokens are valid for 60 minutes. Re-request a token before or after expiry โ there is no refresh-token flow; client-credentials is designed to be repeated cheaply (Argon2id verification ~200ms server-side).
Common errors
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_request | Missing/malformed field (e.g. wrong grant_type). |
| 401 | invalid_client | Tenant/Application not found, secret mismatch, or tenant inactive. |
| 403 | unauthorized_client | Application is disabled. |
| 403 | insufficient_scope | JWT does not have the scope required for the route. |
| 429 | too_many_requests | Token endpoint rate-limit (10/minute per IP). |
JWKS (verify tokens client-side)
Public keys for offline JWT verification are exposed at
GET /.well-known/jwks.json. Cache-control headers indicate
a 24-hour TTL โ refresh on signature mismatch only.