Admin REST API
Server-to-server (S2S) endpoints for managing your application’s users, passkeys, and data. All endpoints require an OAuth2 bearer token — not the publishable key.
Authentication
All admin endpoints require Authorization: Bearer <token> obtained from the OAuth2 client credentials flow.
curl -X POST https://api.trymellonauth.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_APP_ID",
"client_secret": "YOUR_APP_SECRET",
"grant_type": "client_credentials"
}'
Response:
{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }
Use this token in the Authorization header for all admin requests. The token is scoped to one application.
Keep the
client_secretserver-side only. Never expose it in browser code or client-side bundles. Use thepublishableKey(cli_xxxx) for SDK calls from the browser.
Response envelope
All endpoints return a consistent JSON envelope:
{ "ok": true, "data": { ... } } // success
{ "ok": false, "error": { "code": "...", "message": "..." } } // error
Users
Create user
POST /v1/users
Pre-registers a user in your application before they register a passkey. Useful for seeding users from an existing system.
Supports idempotency: pass Idempotency-Key: <uuid> to safely retry without creating duplicates.
Request:
curl -X POST https://api.trymellonauth.com/v1/users \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-..." \
-d '{ "external_user_id": "user_123" }'
Body:
| Field | Type | Description |
|---|---|---|
external_user_id | string (required) | Your user’s ID. 1–255 chars. |
Response 201:
{
"ok": true,
"data": {
"external_user_id": "user_123",
"created_at": "2026-04-07T12:00:00Z",
"updated_at": "2026-04-07T12:00:00Z"
}
}
Get user
GET /v1/users/:external_user_id
Response 200:
{
"ok": true,
"data": {
"id": "uuid",
"external_user_id": "user_123",
"status": "active",
"created_at": "2026-04-07T12:00:00Z",
"updated_at": "2026-04-07T12:00:00Z"
}
}
Returns 404 if the user does not exist.
List users
GET /v1/users
Cursor-based pagination.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page. Default 50, max 100. |
starting_after | UUID | Cursor from the previous page’s next_cursor. |
Response 200:
{
"ok": true,
"data": {
"data": [
{ "id": "uuid", "external_user_id": "user_123", "status": "active", "created_at": "..." }
],
"has_more": true,
"next_cursor": "uuid-of-last-item"
}
}
Delete user
DELETE /v1/users/:external_user_id
Deletes the user and cascades to all their passkeys, sessions, and audit log entries. This operation is irreversible.
Response 204: No content on success.
Credentials (Passkeys)
List credentials for a user
GET /v1/users/:external_user_id/credentials
Returns all passkeys registered by the user, with status and device information.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (offset pagination). Default 1. |
limit | integer | Results per page. |
Response 200:
{
"ok": true,
"data": {
"credentials": [
{
"credential_id": "cred_abc123",
"status": "active",
"alias": "Touch ID — iPhone 15",
"transports": ["internal"],
"attestation_type": "none",
"created_at": "2026-04-01T10:00:00Z",
"last_used_at": "2026-04-07T08:30:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 1 }
}
}
Credential statuses: active, revoked.
Revoke a credential
DELETE /v1/users/:external_user_id/credentials/:credential_id
Revokes a specific passkey. The user will not be able to authenticate with it. Other passkeys registered by the user remain active.
Use this when a user reports a lost device or suspects a compromised authenticator.
Response 204: No content on success.
curl -X DELETE \
"https://api.trymellonauth.com/v1/users/user_123/credentials/cred_abc123" \
-H "Authorization: Bearer <token>"
Audit Logs
List audit logs
GET /v1/audit-logs
Returns a paginated stream of security events for your application.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page. Default 50, max 100. |
starting_after | UUID | Cursor from the previous page’s next_cursor. |
event | string | Filter by event type (see below). |
success | boolean | Filter by outcome (true or false). |
Event types:
The filter accepts every audit event the backend persists. The canonical
list is the AUDIT_EVENT_VALUES array in core/domain/audit/value-objects/audit-event.vo.ts;
the route enum is sourced from that constant so adding an event in the
domain VO automatically opens it for filtering here. The full set today:
| Value | Category | Description |
|---|---|---|
user.created | operation | A user was created |
user.deleted | operation | A user was deleted |
passkey.registered | operation | A passkey was registered |
auth.success | operation | An authentication succeeded |
auth.failed | security | An authentication failed |
credential.revoked | security | A passkey was revoked |
credential.lockout | security | A credential was locked after excessive failed attempts |
credential.replay_detected | security | A sign-count anomaly was detected (possible replay) |
account.recovered | security | An account was recovered via OTP |
onboarding.completed | operation | An onboarding session completed |
registration.started | operation | A registration ceremony started |
subscription.created | billing | A subscription was created |
subscription.updated | billing | A subscription plan changed |
subscription.cancelled | billing | A subscription was cancelled |
subscription.expired | billing | A subscription expired (non-renewal) |
account.plan.changed | billing | An account’s plan tier changed |
action.challenge.issued | operation | An action-signing challenge was issued |
action.signed | security | An action was signed and verified |
action.sign.failed | security | An action-signing verification failed |
attestation.policy.updated | operation | An attestation policy was changed |
attestation.policy.violation | security | A credential violated the active attestation policy |
attestation.credential.blocked | security | A credential was blocked by attestation enforcement |
attestation.mds.cache.refreshed | operation | The FIDO MDS cache was refreshed |
attestation.mds.cache.failed | operation | The FIDO MDS cache refresh failed |
dbsc.session.registered | operation | A device-bound session (DBSC) was registered |
dbsc.session.verified | operation | A DBSC session token was verified |
dbsc.session.invalidated | operation | A DBSC session was invalidated |
dbsc.verification.failed | security | DBSC verification failed |
identifier.linked | operation | An email or wallet identifier was linked to a user |
identifier.unlinked | operation | An identifier was unlinked from a user |
session.exchanged | operation | A maintainer session was exchanged via OAuth2 token-exchange |
session.exchange.denied | security | A token-exchange call was denied (policy / membership) |
acting-as.invoked | security | An actor invoked an endpoint while acting on behalf of a user |
acting-as.ended | security | An acting-as session ended |
application.config_changed | operation | Application settings were updated |
application.secret_rotated | operation | Application client secret was rotated |
authorization.denied | security | A policy decision returned DENY (Principal ADT, ADR-064) |
Response 200:
{
"ok": true,
"data": {
"data": [
{
"id": "uuid",
"event": "auth.success",
"success": true,
"user_id": "uuid",
"ip_address": "203.0.113.42",
"metadata": {},
"created_at": "2026-04-07T09:15:00Z"
}
],
"has_more": false,
"next_cursor": null
}
}
Retention by plan: Starter and Growth — 90 days. Scale and Enterprise — 1 year+. Logs beyond the retention window are automatically purged. Contact support for extended retention options.
Usage
Get usage
GET /v1/usage?period=YYYY-MM
Returns API call counts and active user counts for a billing period.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
period | string (required) | Billing month in YYYY-MM format. E.g. 2026-04. |
Response 200:
{
"ok": true,
"data": {
"tenant_id": "uuid",
"period": "2026-04",
"api_calls": 4820,
"active_users": 312
}
}
Privacy / GDPR
Get user data report
GET /v1/privacy/user-data/:external_user_id
Returns all data stored by TryMellon for a specific user. Use this to fulfill GDPR Article 15 (right of access) requests.
The report includes: user profile, registered credentials, session history, and audit log entries.
curl "https://api.trymellonauth.com/v1/privacy/user-data/user_123" \
-H "Authorization: Bearer <token>"
To delete all data for a user (GDPR Article 17 — right to erasure), use Delete user. Deletion cascades to all associated data.
Rate limits
Admin endpoints are rate-limited per OAuth2 token. On exceeding the limit, the API returns 429 Too Many Requests with a Retry-After header. The SDK’s retry logic handles 429 with exponential backoff automatically; for raw HTTP clients, respect the Retry-After value.
Error responses
Errors share the envelope { "ok": false, "error": { "code": "...", "message": "..." } } and a matching HTTP status.
404 application_not_found
Returned on any application endpoint (GET /v1/applications/:id, PATCH /v1/applications/:id, POST /v1/applications/:id/rotate-secret) when either:
- the application does not exist, or
- it exists but your credentials do not authorize access to it — i.e. you are not the creator, nor an owner of its tenant.
TryMellon returns the same status code for both cases to prevent information disclosure across tenants. This is a deliberate design (ADR-064): an attacker probing for the existence of an app in a foreign tenant sees the same 404 as a missing app.
Operators can correlate internally via the authorization.denied audit trail, which captures the policy name, principal kind (user / service), principal identity, and the resource’s tenant. See Tenants vs accounts for the authorization model.
401 unauthorized
The request lacks a valid Bearer token, a session cookie, or Basic credentials. Fresh-auth flows (e.g. recovery) may also surface 401 when the flow is half-complete.
403 forbidden
Reserved for operations where the caller kind is wrong for the scope — e.g. requesting GET /v1/applications?scope=account with service credentials returns 403 with Account scope requires a user session; service credentials are tenant-scoped.. Authorization denials on per-resource endpoints collapse to 404 (see above) to preserve tenant opacity.
400 validation_error
Body or query did not validate against the schema. The error.message names the offending field.