English
API keys
Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.
GET /api/v1/api-keys
List my API keys
The keys of the signed-in member, live ones first. Never a secret: only the eight-character prefix.
Access — Member session only: API keys are refused (api_key.session_required).
Responses
200 — The keys.
| Field | Type | Required |
|---|---|---|
keys | object[] | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"keys": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"prefix": { "type": "string" },
"scopes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"workflows:read",
"workflows:write",
"workflows:*",
"executions:read",
"executions:write",
"executions:*",
"mailboxes:read",
"mailboxes:write",
"mailboxes:*",
"messages:read",
"messages:write",
"messages:*",
"tables:read",
"tables:write",
"tables:*",
"contacts:read",
"contacts:write",
"contacts:*",
"templates:read",
"templates:write",
"templates:*",
"connections:read",
"connections:write",
"connections:*",
"approvals:read",
"approvals:write",
"approvals:*",
"notifications:read",
"notifications:write",
"notifications:*",
"review:read",
"review:write",
"review:*",
"analyzer:read",
"analyzer:write",
"analyzer:*",
"assistant:read",
"assistant:write",
"assistant:*",
"dashboard:read",
"dashboard:write",
"dashboard:*",
"profile:read",
"profile:write",
"profile:*",
"admin:read",
"admin:write",
"admin:*",
"signals:write"
]
}
},
"memberId": { "type": "string" },
"memberEmail": { "type": "string" },
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"expiresAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"lastUsedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"revokedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
}
},
"required": [
"id",
"name",
"prefix",
"scopes",
"memberId",
"memberEmail",
"createdAt",
"expiresAt",
"lastUsedAt",
"revokedAt"
],
"additionalProperties": false
}
}
},
"required": [ "keys" ],
"additionalProperties": false
}401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
POST /api/v1/api-keys
Create an API key
Creates a key acting on behalf of the signed-in member, limited to the given scopes. The secret is returned once, here, and never again. Scopes reserved to administrators are refused to other members.
Access — Member session only: API keys are refused (api_key.session_required).
Request body (application/json)
| Field | Type | Required |
|---|---|---|
name | string | yes |
scopes | string (enum)[] | yes |
expiresInDays | integer | no |
JSON Schema
json
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1, "maxLength": 120 },
"scopes": {
"minItems": 1,
"maxItems": 20,
"type": "array",
"items": {
"type": "string",
"enum": [
"workflows:read",
"workflows:write",
"workflows:*",
"executions:read",
"executions:write",
"executions:*",
"mailboxes:read",
"mailboxes:write",
"mailboxes:*",
"messages:read",
"messages:write",
"messages:*",
"tables:read",
"tables:write",
"tables:*",
"contacts:read",
"contacts:write",
"contacts:*",
"templates:read",
"templates:write",
"templates:*",
"connections:read",
"connections:write",
"connections:*",
"approvals:read",
"approvals:write",
"approvals:*",
"notifications:read",
"notifications:write",
"notifications:*",
"review:read",
"review:write",
"review:*",
"analyzer:read",
"analyzer:write",
"analyzer:*",
"assistant:read",
"assistant:write",
"assistant:*",
"dashboard:read",
"dashboard:write",
"dashboard:*",
"profile:read",
"profile:write",
"profile:*",
"admin:read",
"admin:write",
"admin:*",
"signals:write"
]
}
},
"expiresInDays": { "type": "integer", "minimum": 1, "maximum": 730 }
},
"required": [ "name", "scopes" ]
}Responses
201 — The key, with its secret.
| Field | Type | Required |
|---|---|---|
key | object | yes |
token | string | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"key": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"prefix": { "type": "string" },
"scopes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"workflows:read",
"workflows:write",
"workflows:*",
"executions:read",
"executions:write",
"executions:*",
"mailboxes:read",
"mailboxes:write",
"mailboxes:*",
"messages:read",
"messages:write",
"messages:*",
"tables:read",
"tables:write",
"tables:*",
"contacts:read",
"contacts:write",
"contacts:*",
"templates:read",
"templates:write",
"templates:*",
"connections:read",
"connections:write",
"connections:*",
"approvals:read",
"approvals:write",
"approvals:*",
"notifications:read",
"notifications:write",
"notifications:*",
"review:read",
"review:write",
"review:*",
"analyzer:read",
"analyzer:write",
"analyzer:*",
"assistant:read",
"assistant:write",
"assistant:*",
"dashboard:read",
"dashboard:write",
"dashboard:*",
"profile:read",
"profile:write",
"profile:*",
"admin:read",
"admin:write",
"admin:*",
"signals:write"
]
}
},
"memberId": { "type": "string" },
"memberEmail": { "type": "string" },
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"expiresAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"lastUsedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"revokedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
}
},
"required": [
"id",
"name",
"prefix",
"scopes",
"memberId",
"memberEmail",
"createdAt",
"expiresAt",
"lastUsedAt",
"revokedAt"
],
"additionalProperties": false
},
"token": { "type": "string" }
},
"required": [ "key", "token" ],
"additionalProperties": false
}400 — The request does not match its schema.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — api_key.bad_request, api_key.scope_forbidden. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
Example request
json
{
"name": "CRM sync",
"scopes": [ "workflows:read", "executions:write" ],
"expiresInDays": 365
}Example response (201)
json
{
"key": {
"id": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "CRM sync",
"prefix": "Qm9uam91",
"scopes": [ "executions:write", "workflows:read" ],
"memberId": "0192f1c2-0000-7000-8000-000000000001",
"memberEmail": "alice@example.test",
"createdAt": "2026-10-04T09:00:00.000Z",
"expiresAt": "2027-10-04T09:00:00.000Z",
"lastUsedAt": null,
"revokedAt": null
},
"token": "mk_Qm9uam91cl9jZXN0X3VuX2V4ZW1wbGVfZGVfY2xl"
}POST /api/v1/api-keys/{id}/revoke
Revoke one of my API keys
Immediate and final. The row stays, for the audit log. Idempotent: revoking twice keeps the first date.
Access — Member session only: API keys are refused (api_key.session_required).
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Responses
200 — The key, revoked.
| Field | Type | Required |
|---|---|---|
key | object | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"key": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"prefix": { "type": "string" },
"scopes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"workflows:read",
"workflows:write",
"workflows:*",
"executions:read",
"executions:write",
"executions:*",
"mailboxes:read",
"mailboxes:write",
"mailboxes:*",
"messages:read",
"messages:write",
"messages:*",
"tables:read",
"tables:write",
"tables:*",
"contacts:read",
"contacts:write",
"contacts:*",
"templates:read",
"templates:write",
"templates:*",
"connections:read",
"connections:write",
"connections:*",
"approvals:read",
"approvals:write",
"approvals:*",
"notifications:read",
"notifications:write",
"notifications:*",
"review:read",
"review:write",
"review:*",
"analyzer:read",
"analyzer:write",
"analyzer:*",
"assistant:read",
"assistant:write",
"assistant:*",
"dashboard:read",
"dashboard:write",
"dashboard:*",
"profile:read",
"profile:write",
"profile:*",
"admin:read",
"admin:write",
"admin:*",
"signals:write"
]
}
},
"memberId": { "type": "string" },
"memberEmail": { "type": "string" },
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"expiresAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"lastUsedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
},
"revokedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
}
},
"required": [
"id",
"name",
"prefix",
"scopes",
"memberId",
"memberEmail",
"createdAt",
"expiresAt",
"lastUsedAt",
"revokedAt"
],
"additionalProperties": false
}
},
"required": [ "key" ],
"additionalProperties": false
}401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — api_key.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
GET /api/v1/admin/api-keys
List every API key of the instance
Access — Member session or API key with scope admin:read (administrator role required).
Responses
200 — The keys.
| Field | Type | Required |
|---|---|---|
keys | object[] | yes |
Same schema as GET /api/v1/api-keys.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
POST /api/v1/admin/api-keys/{id}/revoke
Revoke any API key
Access — Member session or API key with scope admin:write (administrator role required).
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Responses
200 — The key, revoked.
| Field | Type | Required |
|---|---|---|
key | object | yes |
Same schema as POST /api/v1/api-keys/{id}/revoke.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — api_key.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.