API reference

Verify exported records

Stateless verification of exported records: intent envelopes and human-pause checkpoints. Nothing is stored or read; the same arithmetic runs offline.

POST /v1/verify/checkpoint

Verify a human-pause checkpoint record

Checks a checkpoint's signature and responder binding, and whether it resolves exactly the pause you name.

Auth
session cookie or API key
Capability
verify
Success
HTTP 200

Three checks, each named: the signature, the responder binding (responder_id must equal the hash of the supplied key), and, when you name a pause, whether this record resolves exactly that envelope_id and operator_index.

Parameters

None.

Request body

checkpoint object required
No description in the schema.
expect_envelope_id string or null
No description in the schema.
expect_operator_index integer or null
No description in the schema.
responder_pubkey string required
b64url of the raw 32-byte Ed25519 public key

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/verify/checkpoint" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "checkpoint": {
    "decision": "approve",
    "envelope_id": "env-7d3c1a9e5b2f4068",
    "operator_index": 2,
    "responded_at": "2026-09-01T14:15:40+00:00",
    "responder_id": "rZ2h3kLm9Qp4Vt7Wb1Xc6A",
    "signature": "ojBMMJZXLHtKKGlHs-qgpJ9Z15CbP_JS3QRTciXbugiRFbvlR06D_eQqaB0_bMbUQi1vG7lm41rHUTTLyn_usA"
  },
  "expect_envelope_id": "env-7d3c1a9e5b2f4068",
  "expect_operator_index": 2,
  "responder_pubkey": "2zLCoIQX_0RVjg6mV25hzMLGOI2aVqRthf8YsS-Ox3Y"
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "checkpoint": {
        "decision": "approve",
        "envelope_id": "env-7d3c1a9e5b2f4068",
        "operator_index": 2,
        "responded_at": "2026-09-01T14:15:40+00:00",
        "responder_id": "rZ2h3kLm9Qp4Vt7Wb1Xc6A",
        "signature": "ojBMMJZXLHtKKGlHs-qgpJ9Z15CbP_JS3QRTciXbugiRFbvlR06D_eQqaB0_bMbUQi1vG7lm41rHUTTLyn_usA"
    },
    "expect_envelope_id": "env-7d3c1a9e5b2f4068",
    "expect_operator_index": 2,
    "responder_pubkey": "2zLCoIQX_0RVjg6mV25hzMLGOI2aVqRthf8YsS-Ox3Y"
}

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

Example response

HTTP 200
{
  "reason": "ok",
  "responder_bound": true,
  "satisfies": true,
  "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 verify; the body names it.
422validation errorA required field is missing or a value has the wrong type.

What would show this is false

Pass expect_operator_index 3 for a checkpoint that answered pause 2: valid stays true and satisfies is false. An approval for one pause never satisfies another.

POST /v1/verify/envelope

Verify an intent envelope record

Checks an exported envelope's Ed25519 signature against the signer's public key; stores and reads nothing.

Auth
session cookie or API key
Capability
verify
Success
HTTP 200

Reasons: ok, unsigned, bad_public_key, signature_invalid, malformed. The same check runs offline with the same inputs; compare the two reasons.

Parameters

None.

Request body

envelope object required
No description in the schema.
public_key_pem string required
No description in the schema.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/verify/envelope" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "envelope": {
    "action_class": "tool-call",
    "actor": "orchestrator-1",
    "envelope_id": "env-7d3c1a9e5b2f4068",
    "scope": [
      "deploy:staging",
      "repo:read"
    ],
    "signature": "ojBMMJZXLHtKKGlHs-qgpJ9Z15CbP_JS3QRTciXbugiRFbvlR06D_eQqaB0_bMbUQi1vG7lm41rHUTTLyn_usA",
    "signer_id": "sha256:b06ac05c8158d444bc27754779144c5b",
    "temporal": "now"
  },
  "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 = {
    "envelope": {
        "action_class": "tool-call",
        "actor": "orchestrator-1",
        "envelope_id": "env-7d3c1a9e5b2f4068",
        "scope": [
            "deploy:staging",
            "repo:read"
        ],
        "signature": "ojBMMJZXLHtKKGlHs-qgpJ9Z15CbP_JS3QRTciXbugiRFbvlR06D_eQqaB0_bMbUQi1vG7lm41rHUTTLyn_usA",
        "signer_id": "sha256:b06ac05c8158d444bc27754779144c5b",
        "temporal": "now"
    },
    "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAExampleKeyBytesOnlyReplaceMe0000000000=\n-----END PUBLIC KEY-----\n"
}

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

Example response

HTTP 200
{
  "envelope_id": "env-7d3c1a9e5b2f4068",
  "reason": "ok",
  "signer_id": "sha256:b06ac05c8158d444bc27754779144c5b",
  "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 verify; 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 scope entry and resubmit: signature_invalid. Remove the signature: unsigned, which is a different answer from invalid.

POST /v1/verify/scientific-campaign

Verify a research record chain

Checks a chain of signed research records you supply: hashes, signatures under the public key you name, contiguous sequence numbers, parent links, and the declared stage and claim-state transitions. Payload-free: artifact bytes stay with you.

Auth
session cookie or API key
Capability
verify
Success
HTTP 200

Parameters

None.

Request body

campaign_id string required
No description in the schema.
public_key_pem string required
No description in the schema.
records array of object required
No description in the schema.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/verify/scientific-campaign" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "campaign_id": "study-2026-09",
  "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----\n",
  "records": [
    {
      "artifact_digests": {
        "protocol.pdf": "sha256:3b5d0f1e9a7c2b4d6e8f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d"
      },
      "campaign_id": "study-2026-09",
      "claim_state": "proposed",
      "prev_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
      "record_id": "rec-0001",
      "recorded_at": "2026-09-01T09:00:00+00:00",
      "schema": "afa.scientific-campaign-record.v1",
      "sequence": 0,
      "signature": "base64url-ed25519-signature",
      "stage": "preregistered"
    }
  ]
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "campaign_id": "study-2026-09",
    "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----\n",
    "records": [
        {
            "artifact_digests": {
                "protocol.pdf": "sha256:3b5d0f1e9a7c2b4d6e8f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d"
            },
            "campaign_id": "study-2026-09",
            "claim_state": "proposed",
            "prev_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
            "record_id": "rec-0001",
            "recorded_at": "2026-09-01T09:00:00+00:00",
            "schema": "afa.scientific-campaign-record.v1",
            "sequence": 0,
            "signature": "base64url-ed25519-signature",
            "stage": "preregistered"
        }
    ]
}

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

Example response

HTTP 200
{
  "artifact_bytes_verified": false,
  "artifact_verification_boundary": "Record signatures and declared artifact digests were verified. Artifact bytes were not supplied and were not checked.",
  "campaign_id": "study-2026-09",
  "failures": [],
  "latest_claim_state": {
    "record_id": "rec-0001",
    "state": "proposed"
  },
  "raw_payload_stored": false,
  "record_count": 1,
  "schema": "afa.scientific-campaign-verification.v1",
  "signer_fingerprint": "sha256:1cc3ccccd1311190b895e4cc1ad792d053fdf2462527c6ff3af266645060b3d7",
  "signer_trust_assessed": false,
  "signer_trust_boundary": "The caller supplied the public key. Cryptographic validity does not establish who controls it.",
  "stages": [
    "preregistered"
  ],
  "tip": "sha256:8e1f0a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f",
  "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 verify; the body names it.
422validation errorA required field is missing or a value has the wrong type.
422invalid_inputthe key does not parse, is not Ed25519, or the record list is empty or over 10000

What would show this is false

Change one byte of one record and resend: valid turns false and failures names the record. The service stores nothing from this call; run it twice and nothing differs.