API reference

Keys

Operations under the keys group, generated from the API description.

GET /v1/me/api-keys

List keys

Lists the account's keys with prefix, machine, expiry and scopes; never the token.

Auth
session cookie or API key
Capability
keys
Success
HTTP 200

Parameters

None.

Request body

None.

Example request

curl

curl -sS -X GET "https://api.afaprotocol.com/v1/me/api-keys" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs"

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}

r = requests.get(f"{API}/v1/me/api-keys", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 200
{
  "active_count": 1,
  "expiring_soon": [],
  "keys": [
    {
      "created_at": "2026-09-01T14:02:00+00:00",
      "days_until_expiry": 29,
      "expires_at": "2026-10-01T14:02:00+00:00",
      "id": "7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e",
      "label": "deploy runner",
      "last_used_at": "2026-09-01T14:12:07+00:00",
      "machine": "worker-1",
      "prefix": "afa-beta-k3ryn7pq",
      "revoked_at": null,
      "rotate_recommended": false,
      "scopes": [
        "events:write",
        "events:read",
        "grants"
      ]
    }
  ],
  "max": 10
}

Errors

StatusCodeMeaning
401missing_token / invalid_or_expired_api_keyNo credential, an expired session, or a revoked or expired key.
403scope_missingA key limited by scopes lacks keys; the body names it.

What would show this is false

Create a key, revoke it, list again: the row stays with revoked_at set and active_count drops by one.

POST /v1/me/api-keys

Create a per-machine key

Issues a token for one machine and returns it once. The token reads afa-beta-<26 characters>-<4-character check>; the check is the first four characters of the base32 encoding of the CRC-32 of the 26, so a mistyped key is refused at the door with 401 malformed_key rather than reported as an unknown one.

Auth
session cookie only
Capability
keys
Success
HTTP 201

scopes is optional. Omitted or null means every capability. Each entry must be one of the fixed capability names; an unknown one is refused by name.

At most ten active keys per account. Only a session can create, rotate or revoke keys; a key cannot mint another key.

Parameters

None.

Request body

label string or null
No description in the schema.
machine string required
Machine identifier: hostname or operator label
org_id string or null
No description in the schema.
scopes array of string or null
Capabilities this key may call; null means all of them

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/me/api-keys" \
  -H "Cookie: afa_jwt=<session cookie>" \
  -H "Content-Type: application/json" \
  -d '{
  "label": "deploy runner",
  "machine": "worker-1",
  "scopes": [
    "events:write",
    "events:read",
    "grants"
  ]
}'

Python

import requests

API = "https://api.afaprotocol.com"
cookies = {"afa_jwt": "<session cookie>"}
payload = {
    "label": "deploy runner",
    "machine": "worker-1",
    "scopes": [
        "events:write",
        "events:read",
        "grants"
    ]
}

r = requests.post(f"{API}/v1/me/api-keys", cookies=cookies, json=payload, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 201
{
  "key": {
    "created_at": "2026-09-01T14:02:00+00:00",
    "days_until_expiry": 30,
    "expires_at": "2026-10-01T14:02:00+00:00",
    "id": "7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e",
    "label": "deploy runner",
    "last_used_at": null,
    "machine": "worker-1",
    "prefix": "afa-beta-k3ryn7pq",
    "revoked_at": null,
    "rotate_recommended": false,
    "scopes": [
      "events:write",
      "events:read",
      "grants"
    ]
  },
  "token": "afa-beta-k3ryn7pq2wsdz4mvbt6xhc5jfa-e4qs",
  "warning": "Save this token now. It is never displayed again."
}

Errors

StatusCodeMeaning
401missing_token / token_expiredNo session cookie, or one older than eight hours.
422validation errorA required field is missing or a value has the wrong type.
403api_key_cannot_issue_keysThe caller is a key. Keys cannot mint keys.
409max_keys_reachedTen keys are active. Revoke one first.
422unknown_scopeA scopes entry is not a capability; the body lists the known names.

What would show this is false

GET /v1/me/api-keys never lists the token, only its prefix. Only the SHA-256 of the token is stored.

DELETE /v1/me/api-keys/{key_id}

Revoke a key

Revokes one key; its token stops working on the next call.

Auth
session cookie only
Capability
keys
Success
HTTP 204

Parameters

key_id path, string required
No description in the schema.

Request body

None.

Example request

curl

curl -sS -X DELETE "https://api.afaprotocol.com/v1/me/api-keys/7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e" \
  -H "Cookie: afa_jwt=<session cookie>"

Python

import requests

API = "https://api.afaprotocol.com"
cookies = {"afa_jwt": "<session cookie>"}

r = requests.delete(f"{API}/v1/me/api-keys/7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e", cookies=cookies, timeout=30)
r.raise_for_status()
print(r.status_code)  # 204, no body

Example response

HTTP 204
(no body)

Errors

StatusCodeMeaning
401missing_token / token_expiredNo session cookie, or one older than eight hours.
422validation errorA required field is missing or a value has the wrong type.
403api_key_cannot_revoke_keysThe caller is a key.
404key_not_found_or_already_revokedNo active key with this id on this account.

What would show this is false

The revoked token answers 401 invalid_or_expired_api_key on its next call, and a key.revoked row appears in GET /v1/notifications.

POST /v1/me/api-keys/{key_id}/rotate

Rotate a key

Issues a fresh token for the same machine and revokes the old one in the same call.

Auth
session cookie only
Capability
keys
Success
HTTP 201

The new key keeps the machine, label and scopes of the old one and gets a fresh 30-day expiry. A rotation renews the credential, not its reach.

Parameters

key_id path, string required
No description in the schema.

Request body

None.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/me/api-keys/7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e/rotate" \
  -H "Cookie: afa_jwt=<session cookie>"

Python

import requests

API = "https://api.afaprotocol.com"
cookies = {"afa_jwt": "<session cookie>"}

r = requests.post(f"{API}/v1/me/api-keys/7a1c3e5b-2d4f-4a6b-9c8e-0f1a2b3c4d5e/rotate", cookies=cookies, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 201
{
  "key": {
    "created_at": "2026-09-01T16:40:00+00:00",
    "days_until_expiry": 30,
    "expires_at": "2026-10-01T16:40:00+00:00",
    "id": "9c8e0f1a-2b3c-4d4e-8f5a-6b7c8d9e0f1a",
    "label": "deploy runner",
    "last_used_at": null,
    "machine": "worker-1",
    "prefix": "afa-beta-m4tqzs7d",
    "revoked_at": null,
    "rotate_recommended": false,
    "scopes": [
      "events:write",
      "events:read",
      "grants"
    ]
  },
  "token": "afa-beta-m4tqzs7dyv2brk6xnhw3jc5pfa-io3v",
  "warning": "Save this token now. It is never displayed again. The previous token is revoked."
}

Errors

StatusCodeMeaning
401missing_token / token_expiredNo session cookie, or one older than eight hours.
422validation errorA required field is missing or a value has the wrong type.
403api_key_cannot_rotate_keysThe caller is a key.
404key_not_found_or_already_revokedNo active key with this id on this account.

What would show this is false

The old token fails with 401 immediately after. The listing shows the old row revoked and a new row with the same machine.