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.
Parameters
event_idpath, 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
| 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 dedup; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | event_not_found | No 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.
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_scopestring- session | parent | intent Default
"session". intentstring or null- 'fresh' bypasses dedup
parent_idstring or null- scope parent (e.g. intent envelope id)
savings_modestring- maximal (hard-deny all) | conservative (advisory for strict-schema tools) Default
"maximal". toolstring required- No description in the schema.
tool_inputobject- No description in the schema.
window_secondsinteger- 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
| 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 dedup; 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
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.
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_idpath, 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
| 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 dedup; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | event_not_found | No 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.