API reference

Delegation

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

POST /v1/delegations

Sign an order-bound delegation token

Signs a payload under a key derived from the principal chain in the order given, and binds that key to the chain with the service identity.

Auth
session cookie or API key
Capability
delegation
Success
HTTP 200

The same names in another order produce a different key, a different chain_pubkey and a different binding. The root secret never leaves the server; the token carries everything a verifier needs.

Parameters

None.

Request body

chain array of string
No description in the schema.
payload object
No description in the schema.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/delegations" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "chain": [
    "alice",
    "bob",
    "worker-1"
  ],
  "payload": {
    "expires_at": "2026-09-01T18:00:00+00:00",
    "scope": "staging",
    "task": "deploy"
  }
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "chain": [
        "alice",
        "bob",
        "worker-1"
    ],
    "payload": {
        "expires_at": "2026-09-01T18:00:00+00:00",
        "scope": "staging",
        "task": "deploy"
    }
}

r = requests.post(f"{API}/v1/delegations", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 200
{
  "token": {
    "alg": "ed25519-sha256-json-v1",
    "binding": "ed25519:sPHo5uNJgvrLFP80vgnUJoQAfSMHcwwYdpwTpfEQqaTpNvvuDAA3LHfsJRMnWO0XeGCYyUKyEdeskceIi1JbUg",
    "chain": [
      "alice",
      "bob",
      "worker-1"
    ],
    "chain_fingerprint": "sha256:97a891c8f73797af4fc8a2c619aacf378153c11c35d28b2c367bbcbd3d5446b3",
    "chain_pubkey": "F0sqkRw5bYv4o0xRD5XeJ6ue30JMQ575_fmbt51vRGw",
    "payload": {
      "expires_at": "2026-09-01T18:00:00+00:00",
      "scope": "staging",
      "task": "deploy"
    },
    "scheme": "ordered-delegation-v1",
    "signature": "ed25519:rcdluzWwRwPH5g2iR8kWzB8TGqQGj4qI3kFmgmW140qIBs6gn7DpVGktxmdUpCI9r9ZygSUe1KPxANpRe2hUsw"
  }
}

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 delegation; the body names it.
422validation errorA required field is missing or a value has the wrong type.
422invalid_chainchain must be a non-empty list of non-empty strings.

What would show this is false

Sign the same payload with the chain reversed: chain_pubkey differs. Verify the first token after swapping two chain names: chain_order_mismatch.

GET /v1/delegations/service-pubkey

The service identity key

Returns the public key that signs every token's binding, so verification can run without us.

Auth
session cookie or API key
Capability
none needed (the published verification key; a third party needs no account to check a token)
Success
HTTP 200

Fetch once and pin it. A changed value means a changed service identity and voids the pin.

Parameters

None.

Request body

None.

Example request

curl

curl -sS -X GET "https://api.afaprotocol.com/v1/delegations/service-pubkey" \
  -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/delegations/service-pubkey", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 200
{
  "alg": "ed25519",
  "service_pubkey": "WC_iTVveV1LW0d6cf_XTAaYkRZ__8MpC9p2gUcCUPqA",
  "verifies": [
    "binding (chain <-> chain_pubkey, order-exact)",
    "signature (payload under chain_pubkey)"
  ]
}

Errors

StatusCodeMeaning
401missing_token / invalid_or_expired_api_keyNo credential, an expired session, or a revoked or expired key.

What would show this is false

With the pinned key, the offline verifier on the Verify page gives the same reason as POST /v1/delegations/verify for the same token, with the network off.

GET /v1/delegations/service-pubkeys

Service signing keys, with history

Lists every key the service has signed delegation tokens with, current and retired, each with its key id, so a token signed before a rotation still verifies offline.

Auth
session cookie or API key
Capability
none needed (the published key history, so a token signed before a rotation still checks)
Success
HTTP 200

Parameters

None.

Request body

None.

Example request

curl

curl -sS -X GET "https://api.afaprotocol.com/v1/delegations/service-pubkeys" \
  -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/delegations/service-pubkeys", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 200
{
  "active_kid": "4ce1d7e4f549f12f",
  "alg": "ed25519",
  "keys": [
    {
      "active": true,
      "kid": "4ce1d7e4f549f12f",
      "public_key_b64": "Jb_rq0ElquQECrPE8NW3QQobGXTb4faAjhLcrUR_aWc",
      "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAJb/rq0ElquQECrPE8NW3QQobGXTb4faAjhLcrUR/aWc=\n-----END PUBLIC KEY-----\n",
      "retired_at": null,
      "valid_from": null
    }
  ],
  "kid_scheme": "first 16 hex characters of sha256 over the public key DER (SubjectPublicKeyInfo)"
}

Errors

StatusCodeMeaning
401missing_token / invalid_or_expired_api_keyNo credential, an expired session, or a revoked or expired key.

What would show this is false

Recompute the kid from the PEM you receive. A token that names a kid absent from this list is refused with unknown_kid, never verified against a guess.

POST /v1/delegations/verify

Verify a delegation token

Runs the binding check and the payload signature check on a token and names the first that fails.

Auth
session cookie or API key
Capability
delegation
Success
HTTP 200

Reasons: ok, chain_order_mismatch, payload_tampered, binding_invalid, malformed. The hosted answer and the offline answer are the same arithmetic; compare them.

Parameters

None.

Request body

token object required
No description in the schema.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/delegations/verify" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "token": {
    "alg": "ed25519-sha256-json-v1",
    "binding": "ed25519:sPHo5uNJgvrLFP80vgnUJoQAfSMHcwwYdpwTpfEQqaTpNvvuDAA3LHfsJRMnWO0XeGCYyUKyEdeskceIi1JbUg",
    "chain": [
      "alice",
      "bob",
      "worker-1"
    ],
    "chain_fingerprint": "sha256:97a891c8f73797af4fc8a2c619aacf378153c11c35d28b2c367bbcbd3d5446b3",
    "chain_pubkey": "F0sqkRw5bYv4o0xRD5XeJ6ue30JMQ575_fmbt51vRGw",
    "payload": {
      "expires_at": "2026-09-01T18:00:00+00:00",
      "scope": "staging",
      "task": "deploy"
    },
    "scheme": "ordered-delegation-v1",
    "signature": "ed25519:rcdluzWwRwPH5g2iR8kWzB8TGqQGj4qI3kFmgmW140qIBs6gn7DpVGktxmdUpCI9r9ZygSUe1KPxANpRe2hUsw"
  }
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "token": {
        "alg": "ed25519-sha256-json-v1",
        "binding": "ed25519:sPHo5uNJgvrLFP80vgnUJoQAfSMHcwwYdpwTpfEQqaTpNvvuDAA3LHfsJRMnWO0XeGCYyUKyEdeskceIi1JbUg",
        "chain": [
            "alice",
            "bob",
            "worker-1"
        ],
        "chain_fingerprint": "sha256:97a891c8f73797af4fc8a2c619aacf378153c11c35d28b2c367bbcbd3d5446b3",
        "chain_pubkey": "F0sqkRw5bYv4o0xRD5XeJ6ue30JMQ575_fmbt51vRGw",
        "payload": {
            "expires_at": "2026-09-01T18:00:00+00:00",
            "scope": "staging",
            "task": "deploy"
        },
        "scheme": "ordered-delegation-v1",
        "signature": "ed25519:rcdluzWwRwPH5g2iR8kWzB8TGqQGj4qI3kFmgmW140qIBs6gn7DpVGktxmdUpCI9r9ZygSUe1KPxANpRe2hUsw"
    }
}

r = requests.post(f"{API}/v1/delegations/verify", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())

Example response

HTTP 200
{
  "reason": "ok",
  "valid": true
}

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 delegation; the body names it.
422validation errorA required field is missing or a value has the wrong type.

What would show this is false

Change one character of payload.scope: payload_tampered. Swap two chain entries: chain_order_mismatch.