Credence API supports API key authentication for programmatic access with fine-grained scope-based access control.
All keys follow this format:
cr_<64 lowercase hex characters>
Total length: 67 characters. Example:
cr_a3f2b1c0d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
Include the key in one of these headers (X-API-Key takes precedence):
X-API-Key: cr_your_key_hereAuthorization: Bearer cr_your_key_hereEach API key is issued with one or more scopes that control exactly which endpoints it may call. This enforces least-privilege: a key can only do what it was explicitly granted.
| Scope | Description |
|---|---|
trust:read |
Read-only access to trust scores and bond data |
attestations:read |
List and count attestations |
attestations:write |
Create or revoke attestations |
payouts:write |
Initiate payout / settlement operations |
reports:generate |
Trigger and poll report generation jobs |
exports:read |
Download report artifacts and audit-log exports |
webhooks:admin |
Manage webhook signing secrets (rotate / revoke) |
outbox:reinject |
Reinsert fixed quarantined outbox events |
admin:read |
Read admin resources (users, audit logs, failed events, imports previews, presets) |
admin:write |
Mutate admin resources (assign roles, revoke keys, replay events, impersonate, import commits, manage presets) |
flags:read |
Read feature flag configurations and tenant rollouts |
flags:write |
Create, update, and delete feature flags and overrides |
bond:read |
Read bond state and status |
bond:write |
Create and update bond records |
Keys issued before the granular scope model was introduced carry one of two legacy values. They continue to work and are automatically expanded:
| Legacy scope | Expands to |
|---|---|
public |
trust:read, attestations:read |
enterprise |
All granular scopes: trust:read, attestations:read, attestations:write, payouts:write, reports:generate, exports:read, webhooks:admin, outbox:reinject, admin:read, admin:write, flags:read, flags:write, bond:read, bond:write |
| Endpoint | Required scope |
|---|---|
GET /api/trust/* |
trust:read |
GET /api/attestations/:address |
attestations:read |
POST /api/attestations |
attestations:write |
POST /api/payouts |
payouts:write |
GET /api/transactions/history |
trust:read |
POST /api/bulk/verify |
trust:read |
GET /api/reports/top-talkers |
exports:read |
POST /api/reports |
reports:generate |
GET /api/reports/:jobId |
reports:generate |
GET /api/reports/download/:key |
(signed URL — no key required) |
POST /api/imports/preview |
admin:read |
POST /api/imports/preview/:presetId |
admin:read |
POST /api/imports/dry-run |
admin:write |
POST /api/imports/dry-run/:presetId |
admin:write |
POST /api/imports/commit |
admin:write |
POST /api/imports/commit/:presetId |
admin:write |
GET /api/imports/presets |
admin:read |
POST /api/imports/presets |
admin:write |
GET /api/imports/presets/:id |
admin:read |
PUT /api/imports/presets/:id |
admin:write |
DELETE /api/imports/presets/:id |
admin:write |
POST /api/admin/quarantine/:id/reinject |
outbox:reinject |
POST /api/admin/webhooks/:id/rotate |
webhooks:admin (or admin role) |
POST /api/admin/webhooks/:id/revoke-previous |
webhooks:admin (or admin role) |
GET /api/admin/users |
admin role |
GET /api/admin/audit-logs |
admin role |
GET /api/admin/audit-logs/export |
admin role |
POST /api/admin/roles/assign |
admin role |
POST /api/admin/keys/revoke |
admin role |
POST /api/admin/impersonate |
admin role |
POST /api/admin/events/replay/:id |
admin role |
| Tier | Rate limit |
|---|---|
free |
100 req/min |
pro |
1 000 req/min |
enterprise |
10 000 req/min |
POST /api/api-keys
Content-Type: application/json
Authorization: Bearer <key_with_bond:write_scope>
{
"ownerId": "user_abc",
"scopes": ["trust:read", "attestations:read"],
"tier": "free"
}Response (201 — the raw key is only returned here):
{
"id": "3f8a1c2b",
"key": "cr_a3f2b1c0...",
"prefix": "a3f2b1c0",
"scopes": ["trust:read", "attestations:read"],
"scope": "trust:read",
"tier": "free",
"createdAt": "2026-02-24T12:00:00.000Z"
}
scope(singular) is kept for backward compatibility and reflects the first granted scope.
GET /api/api-keys/:ownerId
Authorization: Bearer <valid_api_key>Response omits the raw key and the stored hash:
[
{
"id": "3f8a1c2b",
"prefix": "a3f2b1c0",
"scopes": ["trust:read", "attestations:read"],
"scope": "trust:read",
"tier": "free",
"ownerId": "user_abc",
"createdAt": "2026-02-24T12:00:00.000Z",
"lastUsedAt": null,
"active": true
}
]Revokes the current key and issues a new one with the same scopes and tier:
POST /api/api-keys/:id/rotate
Authorization: Bearer <valid_api_key>Response: same shape as key creation (201), including the new raw key.
DELETE /api/api-keys/:id
Authorization: Bearer <valid_api_key>Response: 204 No Content. Subsequent requests using the revoked key receive 401 Unauthorized.
-
All key validation is performed exclusively against the persistent, hashed database store via
validateApiKey()insrc/services/apiKeys.ts. There are no hardcoded keys, mock users, or plaintext key comparisons anywhere in the authentication code path. -
Keys are stored as SHA-256 hashes — the raw key is never persisted and is shown exactly once.
-
Issue keys with the minimum scopes required for the integration. Do not use
enterpriseunless all operations are needed. -
Rotate keys periodically; compromised keys can be revoked at any time.
-
All key operations (create, revoke, rotate) are logged to the audit log.
-
Rate limits are enforced per tier (integration at the infrastructure layer, e.g. via a reverse proxy or Redis-based limiter).
-
The middleware is deny-by-default: if a key does not carry the required scope, the request is rejected with
403 Forbiddenbefore reaching the handler. -
For complete claim definitions, header specifications, and consumer middleware, see docs/JWT_CLAIMS.md.
-
Timing-safe validation: Keys are validated by hashing the presented key and performing constant-time checks against stored hashes to mitigate timing attacks. Implementations must avoid early-exit string comparisons on raw keys.
-
No logging of raw keys: Never log or persist raw API key values in application logs, error messages, or monitoring systems.
| Status | Body | Cause |
|---|---|---|
| 400 | { "error": "ownerId is required" } |
Missing required field |
| 401 | { "error": "Unauthorized", "message": "API key is required" } |
No key in request headers |
| 401 | { "error": "Unauthorized", "message": "Invalid API key" } |
Key not found, bad format, or revoked |
| 403 | { "error": "Forbidden", "message": "Insufficient scope: '...' is required", "requiredScope": "...", "grantedScopes": [...] } |
Key lacks the required scope |
| 404 | { "error": "Key not found" } |
Unknown key ID |