API reference
Console data
The aggregates and views the console reads. They show what the API knows; nothing is inferred.
GET/v1/dashboard/alertsPOST/v1/dashboard/alerts/{alert_id}/dismissGET/v1/dashboard/dedup-statsGET/v1/dashboard/graphGET/v1/dashboard/machinesGET/v1/dashboard/usage
GET /v1/dashboard/alerts
Active banners
Lists the undismissed in-app alerts, newest first.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/dashboard/alerts" \
-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/dashboard/alerts", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"alerts": [
{
"body": "Sub-agent grant g-0c9f31ab7e2d revoked for 'worker-1'.",
"created_at": "2026-09-01T14:20:00+00:00",
"id": "c4d5e6f7-0a1b-4c2d-8e3f-9a0b1c2d3e4f",
"kind": "subagent_grant_revoked",
"severity": "info"
}
]
}
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 notifications; the body names it. |
What would show this is false
Dismiss an alert and list again: it is gone from this feed, while GET /v1/notifications keeps its dispatch row.
POST /v1/dashboard/alerts/{alert_id}/dismiss
Dismiss a banner
Removes one alert from the active feed.
Parameters
alert_idpath, string required- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/dashboard/alerts/c4d5e6f7-0a1b-4c2d-8e3f-9a0b1c2d3e4f/dismiss" \
-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/dashboard/alerts/c4d5e6f7-0a1b-4c2d-8e3f-9a0b1c2d3e4f/dismiss", headers=headers, timeout=30)
r.raise_for_status()
print(r.status_code) # 204, no body
Example response
HTTP 204
(no body)
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 notifications; the body names it. |
| 422 | validation error | A required field is missing or a value has the wrong type. |
| 404 | alert_not_found | No active alert with this id on this account. |
What would show this is false
Dismiss the same id twice: the second is 404. A dismissed banner cannot be dismissed again.
GET /v1/dashboard/dedup-stats
Dedup counters
Counts cache hits, reuses, misses and bypasses across the account's events.
The response also reports the cache window in events and seconds. These are counts of what the record says happened, not a measured saving.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/dashboard/dedup-stats" \
-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/dashboard/dedup-stats", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"by_action": {
"execute": 5,
"reuse": 1
},
"by_status": {
"hit": 1,
"miss": 2,
"uncacheable": 3
},
"by_tool_hits": {
"Read": 1
},
"bypasses": 0,
"hit_rate": 0.333,
"hits": 1,
"misses": 2,
"reuses": 1,
"scanned": 6,
"uncacheable": 3,
"user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14"
}
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. |
What would show this is false
hit_rate equals hits divided by hits plus misses, rounded to three places. bypasses and uncacheable are excluded from it.
GET /v1/dashboard/graph
Graph slice
Returns a cursor-paginated slice of events as nodes with parent edges, for the console's graph view.
Parameters
cursorquery, integer or null- No description in the schema.
limitquery, integer- Default
200. machinequery, string or null- No description in the schema.
toolquery, string or null- No description in the schema.
typesquery, string or null- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/dashboard/graph?limit=200" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
params = {
"limit": "200"
}
r = requests.get(f"{API}/v1/dashboard/graph", headers=headers, params=params, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"edges": [
{
"from": "env-7d3c1a9e5b2f4068",
"kind": "parent",
"to": "evt-5e6f708192a3b4c5"
}
],
"federation_root": "sha256:fa169e47437c116de26d2e8a91c3cd7c75c91c12cbe418cb3d60804008eb496c",
"machine": null,
"next_cursor": null,
"nodes": [
{
"decision": "allow",
"event_id": "evt-5e6f708192a3b4c5",
"event_type": "tool_call",
"machine_id": "worker-1",
"machine_sequence": 3,
"sequence": 4,
"signature_status": "unsigned",
"tool": "Read",
"ts": "2026-09-01T14:10:21+00:00"
}
],
"user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14"
}
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
Pass machine=worker-1: federation_root is null and every node's machine_id is worker-1.
GET /v1/dashboard/machines
Machines and chain heads
Lists each machine's chain head and event count, plus the root that combines them.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/dashboard/machines" \
-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/dashboard/machines", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"federation_root": "sha256:fa169e47437c116de26d2e8a91c3cd7c75c91c12cbe418cb3d60804008eb496c",
"machines": [
{
"events_count": 5,
"first_seen_at": "2026-08-31T14:12:07+00:00",
"head_merkle": "sha256:bfe83410e270e861dc5c630ca25445b4e256181c4a4f43e0af5a7c53ed921e30",
"head_seq": 5,
"last_event_at": "2026-09-01T14:20:33+00:00",
"machine_id": "worker-1",
"revoked": false
}
],
"user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14"
}
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
Append one event for a machine: its head_seq and events_count rise by one and federation_root changes.
GET /v1/dashboard/usage
Event aggregates for a window
Counts the events ingested in a time window by type, tool and decision.
This counts what was ingested. GET /v1/usage/summary counts what was called; they answer different questions.
Parameters
fromquery, string or null- No description in the schema.
toquery, string or null- No description in the schema.
Request body
None.
Example request
curl
curl -sS -X GET "https://api.afaprotocol.com/v1/dashboard/usage?from=2026-08-25T00%3A00%3A00Z&to=2026-09-01T00%3A00%3A00Z" \
-H "X-API-Key: afa-beta-EXAMPLE-e4qs"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-API-Key": "afa-beta-EXAMPLE-e4qs"}
params = {
"from": "2026-08-25T00:00:00Z",
"to": "2026-09-01T00:00:00Z"
}
r = requests.get(f"{API}/v1/dashboard/usage", headers=headers, params=params, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"by_decision": {
"allow": 6
},
"by_tool": {
"Read": 1,
"deploy": 4
},
"by_type": {
"delegation_grant": 1,
"tool_call": 3,
"tool_result": 2
},
"total": 6,
"user_id": "2f6e1a0c-9b4d-4e8a-8c31-5d7f0a2b9c14",
"window": {
"from": "2026-08-25T00:00:00+00:00",
"to": "2026-09-01T00:00: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 usage; the body names it. |
| 400 | invalid_timestamp / from_after_to | A bound did not parse, or from is after to. |
What would show this is false
total equals the count POST /v1/events/search returns for the same window.