API reference
Operator cron
Operator-only cron invokers behind a shared secret. Not a customer surface; documented so their effects on your account can be explained.
POST/v1/internal/cron/admin-digestPOST/v1/internal/cron/anchorPOST/v1/internal/cron/digestPOST/v1/internal/cron/grant-expiryPOST/v1/internal/cron/key-expiryPOST/v1/internal/cron/webhooks
POST /v1/internal/cron/admin-digest
Operations digest
Sends one plain-text summary of the last 48 hours to the operator's own address: accounts and keys created, calls by capability, notifications by type, grants created and revoked, governance transitions, and what is queued. Not a customer notification, and no customer preference reaches it.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/admin-digest" \
-H "X-Cron-Secret: <operator secret>" \
-H "Content-Type: application/json" \
-d '{}'
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
payload = {}
r = requests.post(f"{API}/v1/internal/cron/admin-digest", headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"counts": {
"accounts_new": 2,
"accounts_total": 12,
"calls_by_capability": {
"events:write": 480,
"grants": 26
},
"calls_total": 506,
"deliveries_failed": 0,
"errors": null,
"errors_status": "not recorded",
"grants_created": 4,
"grants_revoked": 1,
"keys_created": 3,
"mcp_calls": null,
"mcp_calls_status": "not recorded",
"notifications_total": 9,
"since": "2026-09-05T00:00:00+00:00",
"until": "2026-09-07T00:00:00+00:00",
"webhook_queue_pending_now": 0,
"window_hours": 48
},
"delivery_status": "sent",
"provider": "resend",
"recipient": "operator address"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | The shared secret header is absent or does not match. |
| 503 | cron_secret_unconfigured | No secret is configured. The endpoint refuses rather than running open. |
What would show this is false
Three counts read 'not recorded' rather than zero: calls arriving over the tool transport specifically, application errors, and webhook failures across the window. Nothing stores those, and a zero would be a claim about the world where the truth is a claim about the records. Call counts are summed over whole days, and the response names the days it summed.
POST /v1/internal/cron/anchor
Anchor every changed root
Anchors each account whose root moved since its last receipt; unchanged roots are skipped.
Operator-only. With the provider set to none the sweep reports anchoring_disabled and visits nobody; a bad provider name reports provider_misconfigured with the error.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/anchor" \
-H "X-Cron-Secret: <operator secret>"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
r = requests.post(f"{API}/v1/internal/cron/anchor", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"swept": {
"anchor_failed": 0,
"anchored": 2,
"no_root": 0,
"provider": "rekor",
"status": "ok",
"unchanged": 16,
"users": 18
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | Wrong or missing shared secret. |
| 503 | cron_secret_unconfigured | The deployment has no secret; it fails closed rather than open. |
What would show this is false
Run twice with no new events: the second reports every visited account as unchanged.
POST /v1/internal/cron/digest
Run the daily digest
Rolls each account's digest-eligible email notices from the last 24 hours into one email.
Operator-only. Runs once a day at 14:00 UTC.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/digest" \
-H "X-Cron-Secret: <operator secret>"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
r = requests.post(f"{API}/v1/internal/cron/digest", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"swept": {
"digests_sent": 2,
"grouped": {
"rate.limit_hit": 5
},
"users": 18
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | Wrong or missing shared secret. |
| 503 | cron_secret_unconfigured | The deployment has no secret; it fails closed rather than open. |
What would show this is false
Run twice: the second reports digests_sent 0 for accounts the first already rolled up.
POST /v1/internal/cron/grant-expiry
Announce grants expiring within 24 hours
Dispatches grant.expiring_24h for each live sub-agent grant that expires within a day.
Operator-only. Runs every six hours. Expiry itself is still evaluated at the gate; this only announces it early.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/grant-expiry" \
-H "X-Cron-Secret: <operator secret>"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
r = requests.post(f"{API}/v1/internal/cron/grant-expiry", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"swept": {
"expiring": 1,
"notified": 1,
"users": 18
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | Wrong or missing shared secret. |
| 503 | cron_secret_unconfigured | The deployment has no secret; it fails closed rather than open. |
What would show this is false
A retried invocation re-fires nothing: the notice is idempotent per grant and expiry through the dispatch log.
POST /v1/internal/cron/key-expiry
Sweep expiring keys
Warns each account whose key expires within 7 days or 24 hours, once per key and window.
Operator-only. Not a customer surface; listed so the key.expiring notices on your account can be explained.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/key-expiry" \
-H "X-Cron-Secret: <operator secret>"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
r = requests.post(f"{API}/v1/internal/cron/key-expiry", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"swept": {
"scanned": 12,
"warned_24h": 0,
"warned_7d": 1
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | Wrong or missing shared secret. |
| 503 | cron_secret_unconfigured | The deployment has no secret; it fails closed rather than open. |
What would show this is false
A repeat invocation reports zero new warnings, because the dispatch log already holds each key and window.
POST /v1/internal/cron/webhooks
Drain the webhook queue
Posts each queued payload to its endpoint with bounded retry and records sent or failed.
Operator-only. Runs every two minutes. Only a 2xx counts as sent; after three failures an item is recorded failed and dropped.
Parameters
None.
Request body
None.
Example request
curl
curl -sS -X POST "https://api.afaprotocol.com/v1/internal/cron/webhooks" \
-H "X-Cron-Secret: <operator secret>"
Python
import requests
API = "https://api.afaprotocol.com"
headers = {"X-Cron-Secret": "<operator secret>"}
r = requests.post(f"{API}/v1/internal/cron/webhooks", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
Example response
HTTP 200
{
"webhooks": {
"drained": 3,
"failed": 1,
"sent": 2
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 / 503 | unauthorized / cron_secret_unconfigured | Wrong shared secret, or the deployment has none configured. |
| 401 | unauthorized | Wrong or missing shared secret. |
| 503 | cron_secret_unconfigured | The deployment has no secret; it fails closed rather than open. |
What would show this is false
After a drain, GET /v1/notifications on the affected account shows the webhook rows as sent or failed, not queued.