HTTP API
The machine-readable spec is /openapi.json (OpenAPI 3.1). Import it into Postman, Insomnia or a client generator. It is tested against the server's route table, so every route is in it.
Authentication
| Scheme | Header | Who |
|---|---|---|
admin |
Authorization: Bearer <AGENTGUARD_ADMIN_TOKEN> |
The owner / console |
agent |
Authorization: Bearer ag_… |
An agent key from POST /v1/agents |
cron |
Authorization: Bearer <CRON_SECRET> |
The scheduler (Supabase pg_cron or Vercel cron) |
| none | — | Public keys, anchors, enrollment and passkey ceremonies, health |
Wrong or missing tokens get 401 {"error":"unauthorized"}.
Conventions
- JSON in and out. Amounts are decimal strings (
"10.80"), currencies ISO 4217. - Errors are
{"error": "message"}with a 4xx/5xx status.503while the kill-switch is on. POST /v1/agent/paymentsrequires anidempotency_key. Replaying it with the same proposal returns the original result; with a different proposal it is rejected with409.- Agents are rate limited (
429), 30 requests per minute by default.
A payment, end to end
curl -s -X POST $U/v1/agent/payments -H "authorization: Bearer $AGENT_KEY" -H 'content-type: application/json' -d '{
"agent_id": "shopper", "mandate_id": "'$MID'", "idempotency_key": "order-1001",
"paypal_order_id": "5O190127TN364715T", "agent_rationale": "API credits"}'
curl -s $U/v1/agent/payments/$PID -H "authorization: Bearer $AGENT_KEY"
curl -s $U/v1/agent/payments/$PID/receipt -H "authorization: Bearer $AGENT_KEY"The response carries decision (approve, reject, needs_human), reasons and, once captured, the PayPal ids. See Payment flow.
Reference
Meta
GET /health
Health: ok, stuck payment count, last recovery run. Auth: None.
GET /healthz
Liveness probe (plain 200). Auth: None.
HEAD /healthz
Liveness probe, no body. Auth: None.
Jobs
GET /api/cron/anchor
Timestamp pending receipts and anchor the audit head (Bearer CRON_SECRET). Auth: CRON_SECRET.
POST /api/cron/anchor
Same as GET, for pg_net callers. Auth: CRON_SECRET.
GET /api/cron/maintenance
Expire recovery locks, retry notifications, converge stuck payments. Auth: CRON_SECRET.
POST /api/cron/maintenance
Same as GET, for pg_net callers. Auth: CRON_SECRET.
Keys
GET /.well-known/agentguard-key
Active issuer public key (legacy single-key form). Auth: None.
GET /.well-known/agentguard-keys
Issuer key set: every key with kid, status, created_at, retired_at, compromised_at. Pin this.. Auth: None.
POST /v1/keys/rotate
Retire the active issuer key and create a new one (audited). Auth: Admin token.
POST /v1/keys/{kid}/compromised
Mark a key compromised as of at (rotates first if active). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
at |
string | ISO time; default now |
Receipts
GET /v1/public/receipts/{id}
Public receipt lookup (only when AGENTGUARD_PUBLIC_RECEIPTS=1). Auth: None.
GET /v1/payments/{id}/receipt
Receipt for a payment. Auth: Admin token.
GET /v1/receipts
List receipts. Auth: Admin token.
Query: limit (Default 100).
GET /v1/receipts/{id}
Receipt by id. Auth: Admin token.
Setup
POST /v1/agents
Register an agent and return its API key (shown once). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
user_id |
string | yes | Owner user id |
agent_id |
string | yes | Agent id |
POST /v1/wallets/setup
Start PayPal wallet vaulting; returns the setup token with the buyer consent link. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
user_id |
string | yes |
GET /v1/wallets/return
PayPal redirect target after buyer consent; links the vaulted wallet. Auth: None.
Query: state (One-time state from /v1/wallets/setup), approval_token_id (PayPal setup token id).
POST /v1/wallets/link
Link an existing PayPal vault id to a user. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
user_id |
string | yes | |
vault_id |
string | yes | |
payer_email |
string |
Mandates
POST /v1/mandates
Create a draft mandate from structured constraints. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
user_id |
string | yes | |
agent_id |
string | yes | |
purpose |
string | yes | |
ttl_minutes |
integer | ||
constraints |
MandateConstraints |
POST /v1/mandates/draft-from-text
Draft a mandate from plain English (model proposes, deterministic parser bounds it). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
user_id |
string | yes | |
agent_id |
string | yes | |
text |
string | yes |
GET /v1/mandates
List mandates with spend state and summary. Auth: Admin token.
GET /v1/mandates/{id}
Mandate, status and spend. Auth: Admin token.
POST /v1/mandates/{id}/sign-request
Get WebAuthn options whose challenge is the mandate hash. Auth: Admin token.
POST /v1/mandates/{id}/confirm
Activate a mandate with the user's passkey assertion. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
mandate_hash |
string | yes | |
assertion |
object | yes | WebAuthn assertion (navigator.credentials.get response, JSON-encoded) over the challenge returned by the matching *-request call |
POST /v1/mandates/{id}/revoke-request
Get WebAuthn options for revocation. Auth: Admin token.
POST /v1/mandates/{id}/revoke
Revoke with the user's passkey (drafts can be discarded without one). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
assertion |
object | WebAuthn assertion (navigator.credentials.get response, JSON-encoded) over the challenge returned by the matching *-request call | |
reason |
string |
Passkeys
GET /v1/users/{id}/passkey
Whether the user has a passkey, and its credential id. Auth: Admin token.
POST /v1/users/{id}/enrollments
Issue a single-use enrollment link (15 min default). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
issued_by |
string | ||
ttl_minutes |
integer |
POST /v1/enroll/options
Redeem step 1: registration options for an enrollment token. Auth: None.
| Field | Type | Required | Notes |
|---|---|---|---|
token |
string | yes |
POST /v1/enroll
Redeem step 2: register the passkey. Auth: None.
| Field | Type | Required | Notes |
|---|---|---|---|
token |
string | yes | |
response |
object | yes | navigator.credentials.create response |
POST /v1/users/{id}/reenroll-request
Options for replacing a passkey with the current one. Auth: Admin token.
POST /v1/users/{id}/reenroll
Mint a re-enrollment token authorized by the current passkey. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
assertion |
object | yes | WebAuthn assertion (navigator.credentials.get response, JSON-encoded) over the challenge returned by the matching *-request call |
GET /v1/users/{id}/recovery
Time-locked recovery state. Auth: Admin token.
POST /v1/users/{id}/recovery/cancel-request
Options to cancel a recovery with the current passkey. Auth: None.
POST /v1/users/{id}/recovery/cancel
Cancel a pending recovery. Auth: None.
| Field | Type | Required | Notes |
|---|---|---|---|
assertion |
object | yes | WebAuthn assertion (navigator.credentials.get response, JSON-encoded) over the challenge returned by the matching *-request call |
Approvals
GET /v1/approvals
Payments held for the owner. Auth: Admin token.
POST /v1/approvals/{id}/request
Approval statement and WebAuthn options (5-minute nonce). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
approve |
boolean | yes |
POST /v1/approvals/{id}
Approve or deny with the user's passkey. The admin token alone is refused.. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
approve |
boolean | yes | |
assertion |
object | yes | WebAuthn assertion (navigator.credentials.get response, JSON-encoded) over the challenge returned by the matching *-request call |
approver |
string |
Agent
POST /v1/agent/payments
Propose a payment (30/min per agent; 429 above). Auth: Agent key.
| Field | Type | Required | Notes |
|---|---|---|---|
mandate_id |
string | yes | |
agent_id |
string | yes | |
idempotency_key |
string | yes | |
paypal_order_id |
string | Merchant-created AUTHORIZE order (preferred). Do not combine with payee/currency/items. | |
payee |
object | Vaulted path only (untrusted) | |
currency |
string | Vaulted path only | |
items |
array | Vaulted path only (untrusted) | |
context |
array | Untrusted context the agent saw; scored for injection, advisory only | |
agent_rationale |
string |
POST /v1/agent/products/search
Search the Channel3 product catalog within the mandate's currency and remaining budget (discovery only; never pays). Auth: Agent key.
| Field | Type | Required | Notes |
|---|---|---|---|
mandate_id |
string | yes | |
query |
string | yes | |
limit |
integer | 1-30, default 8 | |
page_token |
string | next_page_token from a previous search |
GET /v1/agent/payments/{id}
Payment status (the calling agent's own only). Auth: Agent key.
GET /v1/agent/payments/{id}/receipt
Proof-of-Intent receipt for a captured payment. Auth: Agent key.
GET /v1/agent/mandates/{id}
The agent's own mandate, status and spend. Auth: Agent key.
Catalog
POST /v1/products/search
Owner view of the same Channel3 search, with an advisory mandate-fit check per product. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
mandate_id |
string | yes | |
query |
string | yes | |
limit |
integer | ||
page_token |
string |
GET /v1/products/status
Whether Channel3 product search is configured (CHANNEL3_API_KEY). Auth: Admin token.
Payments
GET /v1/payments
List payments. Auth: Admin token.
Query: limit (Default 200).
GET /v1/payments/stuck
Payments in reserved/authorized older than a threshold. Auth: Admin token.
Query: older_than_ms (Default 60000).
POST /v1/recover
Converge stuck payments with PayPal now. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
older_than_ms |
integer |
Control
GET /v1/control/pause
Kill-switch state. Auth: Admin token.
POST /v1/control/pause
Block every new proposal and approval (503). Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
reason |
string | ||
by |
string |
POST /v1/control/resume
Lift the kill-switch. Auth: Admin token.
| Field | Type | Required | Notes |
|---|---|---|---|
by |
string |
Audit
GET /v1/audit
Hash-chained audit log. Auth: Admin token.
Query: limit (Default 200).
GET /v1/audit/verify
Recompute and verify the whole chain. Auth: Admin token.
GET /v1/audit/attestation
Chain head signed with the active issuer key. Auth: Admin token.
POST /v1/audit/anchor
Anchor the chain head now (RFC 6962 Merkle root, RFC 3161 stamped). Auth: Admin token.
GET /v1/audit/anchors
Published anchors; keep copies to detect forks. Auth: None.
Query: limit (Default 100).
MandateConstraints
| Field | Type | Required | Notes |
|---|---|---|---|
currency |
string | yes | ISO 4217 |
max_total |
string | yes | |
max_per_transaction |
string | ||
max_transactions |
integer | yes | |
allowed_categories |
array of PHYSICAL_GOODS, DIGITAL_GOODS, DONATION, UNSPECIFIED | ||
payee_allowlist |
array | ||
payee_blocklist |
array | ||
max_quantity_per_item |
integer | ||
require_human_approval_over |
string | ||
require_merchant_order |
boolean | ||
sku_allowlist |
array |