3 endpoints.
| Method | Path | Summary |
|---|---|---|
GET | /api/communities/{communityId}/api-keys | List this community's API keys |
POST | /api/communities/{communityId}/api-keys | Create an API key |
DELETE | /api/communities/{communityId}/api-keys/{apiKeyId} | Revoke an API key |
GET /api/communities/{communityId}/api-keysNever returns a key or a hash. Revoked keys are included on purpose — "revoked on the 4th, last used on the 3rd" is the sentence an incident review needs, and hiding the row deletes the evidence.
Auth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
communityId | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | The community's API keys, newest first | object |
403 | The caller lacks api_keys:manage, or authenticated with an API key (keys cannot manage keys). | — |
POST /api/communities/{communityId}/api-keysThe response carries a plaintext key EXACTLY ONCE. There is no route that reads it back and no reveal button — if it is lost, revoke this key and create another.
The key acts as the user who created it and can never exceed that member's own permissions. If their membership is removed, un-approved, banned or suspended, the key stops authenticating immediately.
Scopes cannot be changed later. Widening a live key's power without re-issuing it would make the audit trail untrue, so rotation is create-then-revoke.
Auth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
communityId | path | yes | string (uuid) | — |
Request body (required) — application/json
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human label. This is what the owner reads when deciding what to revoke, so make it say who holds it. |
scopes | enum: read, write[] | no | Defaults to read and write. A read-only key is rejected with 403 on any non-GET request. |
expiresAt | string (date-time) | no | Optional. Must be in the future and within ten years. Omit for a key that never expires. |
Responses
| Status | Description | Body |
|---|---|---|
201 | Created. data.key is present here and never again. | object |
400 | Invalid name, unknown scope, or a bad expiry | — |
409 | This community already holds the maximum number of live keys. Revoke one first. | — |
429 | Rate limited — 10 burst, roughly 1 per 10 seconds | — |
DELETE /api/communities/{communityId}/api-keys/{apiKeyId}Takes effect on the very next request that presents the key. There is no cache on the authentication path, deliberately — the one moment anyone revokes a key is the moment they know it has leaked, and "effective within N seconds" is not revocation. One-way. A revoked key cannot be un-revoked; create a new one. The row is kept rather than deleted so its prefix and last-used timestamp remain available as evidence. Idempotent — revoking an already-revoked key returns 200 with the same row, not an error.
Auth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
communityId | path | yes | string (uuid) | — |
apiKeyId | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | The revoked key, with revokedAt set | object |
404 | No such key in this community | — |