API reference
Signing identity and settlement chain
Register a machine's Ed25519 public key, then fold events into signed settlement blocks and verify the block chain. This is what makes the record checkable without us.
GET/v1/chain/blocksPOST/v1/chain/checkpointPOST/v1/chain/verifyGET/v1/identityPOST/v1/identity/register
GET /v1/chain/blocks
List settlement blocks
Lists the account's settlement blocks in order.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/chain/blocks" \
-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/chain/blocks", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"blocks": [
{
"block_hash": "sha256:9263dd51468659ff544714898f6fa402406ae9bdedcb6a18b4a18d3ba30f7818",
"block_index": 0,
"created_at": 1756735927.418,
"event_count": 2,
"machine_id": null,
"merkle_root": "sha256:95561b90e5b2564f9c8885d6b272a6bc82ccc056de0ce72e2713c20020eaca54",
"prev_block_hash": null,
"range_end": 1,
"range_start": 0,
"signature": null,
"signer_id": null
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks events:read; the body names it. |
What would show this is false
Each block's prev_block_hash equals the previous block's block_hash, and the first block's is null.
POST /v1/chain/checkpoint
Fold events into a settlement block
Folds every event since the last block into a new block whose merkle root covers their payload hashes.
With nothing unsettled the response is status noop with settled_through_sequence.
signature, when given, is your Ed25519 signature over merkle_root followed by prev_block_hash. The server stores it and does not verify it; verifying it against your own registered key is what makes the block independent of us.
Parameters
None.
Request body
machine_idstring or null- No description in the schema.
signaturestring or null- client Ed25519 over the block merkle_root||prev_block_hash
signer_idstring or null- No description in the schema.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/chain/checkpoint" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
-H "Content-Type: application/json" \
-d '{
"machine_id": null,
"signature": null,
"signer_id": null
}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
"machine_id": None,
"signature": None,
"signer_id": None
}
r = requests.post(f"{API}/v1/chain/checkpoint", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"block": {
"block_hash": "sha256:9263dd51468659ff544714898f6fa402406ae9bdedcb6a18b4a18d3ba30f7818",
"block_index": 0,
"created_at": 1756735927.418,
"event_count": 2,
"machine_id": null,
"merkle_root": "sha256:95561b90e5b2564f9c8885d6b272a6bc82ccc056de0ce72e2713c20020eaca54",
"prev_block_hash": null,
"range_end": 1,
"range_start": 0,
"signature": null,
"signer_id": null
},
"status": "checkpointed"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks events:write; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
What would show this is false
Recompute merkle_root from the sorted payload hashes of the covered events with the recipe on the Verify page. A different value means the block does not cover the events you think it does.
POST /v1/chain/verify
Verify the block chain
Recomputes every block hash and every link and names the first block that fails.
This checks the blocks against themselves. It does not verify the client signatures on blocks; that check is yours to run with your own key.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/chain/verify" \
-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.post(f"{API}/v1/chain/verify", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"block_count": 1,
"broken": [],
"valid": true
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks verify; the body names it. |
What would show this is false
A stored block edited below the API shows up as block_hash_mismatch at its index. valid true with block_count 0 means nothing has been checkpointed yet, which is a different fact.
GET /v1/identity
List registered keys
Lists the machines with a registered signing key and each key's fingerprint.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/identity" \
-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/identity", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"identities": [
{
"fingerprint": "sha256:b06ac05c8158d444bc27754779144c5b",
"machine_id": "worker-1",
"registered_at": "2026-09-01T14:05:00+00:00"
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks keys; the body names it. |
What would show this is false
The fingerprint is the SHA-256 of the PEM bytes you registered, first 32 hex characters. Recompute it.
POST /v1/identity/register
Register a machine's signing key
Stores a machine's Ed25519 public key so events from that machine must be signed by it.
From this point every POST /v1/events for the machine must carry a signature that verifies over raw, or the append is refused. Registering again rotates the key.
Parameters
None.
Request body
machine_idstring required- No description in the schema.
public_key_pemstring required- No description in the schema.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/identity/register" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
-H "Content-Type: application/json" \
-d '{
"machine_id": "worker-1",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAExampleKeyBytesOnlyReplaceMe0000000000=\n-----END PUBLIC KEY-----\n"
}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
"machine_id": "worker-1",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAExampleKeyBytesOnlyReplaceMe0000000000=\n-----END PUBLIC KEY-----\n"
}
r = requests.post(f"{API}/v1/identity/register", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"fingerprint": "sha256:b06ac05c8158d444bc27754779144c5b",
"machine_id": "worker-1",
"registered_at": "2026-09-01T14:05:00+00:00",
"status": "registered"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token / invalid_or_expired_api_key | No credential, an expired session, or a revoked or expired key. |
| 403 | scope_missing | A key limited by scopes lacks keys; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 400 | invalid_ed25519_public_key | The PEM did not parse as an Ed25519 public key. |
What would show this is false
Register a key, then post an unsigned event for that machine: 401 event_signature_invalid with reason signature_mismatch.