API reference

Dedup and membership

Ask whether a tool call already happened under this scope, and get an inclusion proof for a stored event. Read-only tools only; nothing here changes the record.

GET /v1/cache/{event_id}

Cached-call metadata for one event

Returns the dedup citation, signature status and cache window stored with an event; never the tool output.

Auth
session cookie or API key
Capability
dedup
Success
HTTP 200

Parameters

event_id path, string required
No description in the schema.

Request body

None.

Example request

curl

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

Example response

HTTP 200
{
  "cache_scope": "parent",
  "cache_window": {
    "seconds": 21600
  },
  "cached_from_event_id": "evt-4d5e6f708192a3b4",
  "cached_from_sequence": 2,
  "decision": "allow",
  "dedup_action": "reuse",
  "dedup_key": "sha256:12ba8b0e2703a0156eb1339f44574881babf4b3af933d2b56faa3b6fd6f3d04f",
  "dedup_status": "hit",
  "event_id": "evt-5e6f708192a3b4c5",
  "intent": "cached_ok",
  "note": "Tool response body is not stored on the server by design (raw_payload_stored: False). The cited prior event proves the call already executed; the tool's actual output lives in the agent's own conversation context from when it ran.",
  "result_source": "cache",
  "sequence": 4,
  "signature": null,
  "signature_key": null,
  "tool": "Read",
  "tool_input_hash": "sha256:af8c4ea2176c934432992c1bf696306e853adb58088eb786b979e1d806ef4cae",
  "ts": "2026-09-01T14:10:21+00:00"
}

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 dedup; the body names it.
422validation errorA required field is missing or a value has the wrong type.
404event_not_foundNo such event on this account.

What would show this is false

No field in the response carries the tool's output. Search it for any content you expected the tool to return; it is not there.

POST /v1/dedup/check

Has this call already happened

Evaluates a candidate tool call against the account's record and answers allow or deny with the prior event it cites.

Auth
session cookie or API key
Capability
dedup
Success
HTTP 200

Only read-only, idempotent tools are cacheable; other tools answer uncacheable and execute. intent fresh bypasses the check.

dedup_key is the SHA-256 of the canonical tool name and input, computed the same way on the client side, so a key made on either side matches.

scope_proven compares a proof against a root this server computed from the same read. anchor says whether a client-signed settlement block covers the cited event; that is the independent check.

Parameters

None.

Request body

cache_scope string
session | parent | intent Default "session".
intent string or null
'fresh' bypasses dedup
parent_id string or null
scope parent (e.g. intent envelope id)
savings_mode string
maximal (hard-deny all) | conservative (advisory for strict-schema tools) Default "maximal".
tool string required
No description in the schema.
tool_input object
No description in the schema.
window_seconds integer
Default 21600.

Example request

curl

curl -sS -X POST "https://api.afaprotocol.com/v1/dedup/check" \
  -H "X-API-Key: afa-beta-EXAMPLE-e4qs" \
  -H "Content-Type: application/json" \
  -d '{
  "cache_scope": "parent",
  "parent_id": "env-7d3c1a9e5b2f4068",
  "tool": "Read",
  "tool_input": {
    "file_path": "/srv/app/config.toml"
  },
  "window_seconds": 21600
}'

Python

import requests

API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
payload = {
    "cache_scope": "parent",
    "parent_id": "env-7d3c1a9e5b2f4068",
    "tool": "Read",
    "tool_input": {
        "file_path": "/srv/app/config.toml"
    },
    "window_seconds": 21600
}

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

Example response

HTTP 200
{
  "anchor": {
    "covering_block_index": null,
    "note": "no client-signed settlement block covers this sequence: the membership proof verifies against a root this server computed, which detects tampering below the API but is not independent of it. Append a settlement block over this range to close that.",
    "status": "self_consistent_only"
  },
  "cache_scope": "parent",
  "cached_from_event_id": "evt-5e6f708192a3b4c5",
  "cached_from_sequence": 4,
  "decision": "deny",
  "dedup_action": "reuse",
  "dedup_key": "sha256:12ba8b0e2703a0156eb1339f44574881babf4b3af933d2b56faa3b6fd6f3d04f",
  "dedup_status": "hit",
  "intent": "cached_ok",
  "membership_proof": {
    "event_id": "evt-5e6f708192a3b4c5",
    "leaf": "sha256:af8c4ea2176c934432992c1bf696306e853adb58088eb786b979e1d806ef4cae|sha256:9cea66bcc81b176630bae4106496e28a94bb8766ae48f8c98ef718ba10108080",
    "parent_id": "env-7d3c1a9e5b2f4068",
    "root": "sha256:bfe83410e270e861dc5c630ca25445b4e256181c4a4f43e0af5a7c53ed921e30",
    "sibling_leaves": [
      "sha256:cc779945f428efd318761deb12d725142799242a7a278d91503e0e3c471cd601|sha256:bbaf79f09f84312de611fdfe5fbb3494db9b960c4a583bbb467d492625dc2097"
    ]
  },
  "savings_mode": "maximal",
  "scope_parent_id": "env-7d3c1a9e5b2f4068",
  "scope_proven": 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 dedup; the body names it.
422validation errorA required field is missing or a value has the wrong type.

What would show this is false

Send the same body with intent fresh: dedup_status bypass, decision allow, and no prior event is cited.

GET /v1/dedup/membership/{event_id}

Inclusion proof for one event

Returns a merkle inclusion proof for a stored event under its parent scope, plus whether a client-signed block covers it.

Auth
session cookie or API key
Capability
dedup
Success
HTTP 200

An event without a parent_id has no enclosing scope. The response then carries membership_proof null and proven false, and says why.

Parameters

event_id path, string required
No description in the schema.

Request body

None.

Example request

curl

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

Example response

HTTP 200
{
  "anchor": {
    "covering_block_index": null,
    "note": "no client-signed settlement block covers this sequence: the membership proof verifies against a root this server computed, which detects tampering below the API but is not independent of it. Append a settlement block over this range to close that.",
    "status": "self_consistent_only"
  },
  "event_id": "evt-5e6f708192a3b4c5",
  "membership_proof": {
    "event_id": "evt-5e6f708192a3b4c5",
    "leaf": "sha256:af8c4ea2176c934432992c1bf696306e853adb58088eb786b979e1d806ef4cae|sha256:9cea66bcc81b176630bae4106496e28a94bb8766ae48f8c98ef718ba10108080",
    "parent_id": "env-7d3c1a9e5b2f4068",
    "root": "sha256:bfe83410e270e861dc5c630ca25445b4e256181c4a4f43e0af5a7c53ed921e30",
    "sibling_leaves": [
      "sha256:cc779945f428efd318761deb12d725142799242a7a278d91503e0e3c471cd601|sha256:bbaf79f09f84312de611fdfe5fbb3494db9b960c4a583bbb467d492625dc2097"
    ]
  },
  "proven": 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 dedup; the body names it.
422validation errorA required field is missing or a value has the wrong type.
404event_not_foundNo such event on this account.

What would show this is false

Recompute the root from leaf and sibling_leaves with the recipe on the Verify page. proven is true only when it equals root.