API reference

Agreements

The agreement texts with their hashes, and the acceptances recorded for your account. The text route serves the exact bytes the hash covers.

POST /v1/legal/accept

Record acceptance of a version

Records that this account accepted one specific version of one document, for an account that already exists.

Auth
session cookie or API key
Capability
none needed (agreement texts and acceptances; consulted before an account exists)
Success
HTTP 200

This route needs a signed-in caller, so a new account's first acceptance rides on POST /v1/auth/request-otp instead. Use this one when a document version moves.

Parameters

None.

Request body

accepted boolean required
No description in the schema.
content_hash string required
No description in the schema.
document_id string required
No description in the schema.
version string required
No description in the schema.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/legal/accept" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "accepted": true,
  "content_hash": "1c0cd773e3d89014b809b43966434eac0fab2b701d7f25877f51e165ca320b03",
  "document_id": "beta_terms",
  "version": "2026-09-08.1"
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "accepted": True,
    "content_hash": "1c0cd773e3d89014b809b43966434eac0fab2b701d7f25877f51e165ca320b03",
    "document_id": "beta_terms",
    "version": "2026-09-08.1"
}

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

Example response

HTTP 200
{
  "acceptance": {
    "content_hash": "1c0cd773e3d89014b809b43966434eac0fab2b701d7f25877f51e165ca320b03",
    "document_id": "beta_terms",
    "email": "ops@example.com",
    "recorded_at": "2026-09-01T14:01:30+00:00",
    "source": "portal",
    "status": "accepted",
    "user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14",
    "version": "2026-09-08.1"
  },
  "status": "recorded",
  "unmet_document_ids": [
    "acceptable_use",
    "privacy_notice"
  ]
}

Errors

StatusCodeMeaning
401missing_token / invalid_or_expired_api_keyNo credential, an expired session, or a revoked or expired key.
422validation errorA required field is missing or a value has the wrong type.
400account_has_no_emailThe account cannot be tied to a receipt.
422legal-attestation-invalidWrong version or a hash that does not match the served text; nothing recorded.

What would show this is false

Send a stale version: 422 naming the current version, nothing is recorded, and GET /v1/legal/acceptances still lists the document as unmet.