POST /api/quote
Most callers should use
canopy.check(url)instead. This page is for SDK builders and developers calling the wire protocol directly.
POST /api/quote performs a server-side probe of a paywalled URL, parses the resulting 402 (x402 or MPP), runs the agent's policy in dry-run mode, and returns the parsed offer plus an allowed / pending_approval / denied verdict. Never signs. Cached per (org, url, method) for 60 seconds.
Base URL: https://trycanopy.ai
Request
POST /api/quote
Authorization: Bearer <apiKey>
Content-Type: application/jsonBody
| Field | Type | Description |
|---|---|---|
url | string | Resource URL to probe. https:// (or http:// in dev). Loopback / RFC1918 hosts are rejected. |
agent_id | string | The agent ID (agt_…) whose policy should evaluate the offer. |
method | "GET" | "POST" | Optional. HTTP method to probe with. Defaults to GET; falls back to POST on 405. |
Common response fields
Every successful response (200, 202, or 403) carries the parsed offer:
| Field | Type | Description |
|---|---|---|
status | "allowed" | "pending_approval" | "denied" | Policy verdict in dry-run mode. |
rail | "x402" | "mpp" | Which payment protocol the URL uses. |
chain_id | integer | 8453 for Base, 4217 for Tempo. |
amount_usd | number | Price parsed from the 402 challenge. |
recipient.address | string | On-chain recipient (server-derived from offer / challenge). |
recipient.slug | string | null | Service slug from the registry, when the realm is registered. |
recipient.name | string | null | Service name from the registry. |
resource_url | string | The probed URL, echoed for convenience. |
scheme | string | null | x402 only — typically "exact". |
network | string | Wire network identifier ("base", "tempo", "eip155:8453"). |
realm | string | null | RFC 7235 auth-realm from the MPP WWW-Authenticate; null on x402. |
cached | boolean | True when the response came from the per-(org, url) 60s cache. |
pending_approval adds reason: string and approval_threshold_usd: number | null.
denied adds reason: string.
200 — allowed
{
"status": "allowed",
"rail": "x402",
"chain_id": 8453,
"amount_usd": 0.10,
"recipient": {
"address": "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97",
"slug": "stableenrich",
"name": "StableEnrich"
},
"resource_url": "https://stableenrich.dev/api/apollo/people-enrich",
"scheme": "exact",
"network": "base",
"realm": null,
"cached": false
}202 — pending_approval
The amount exceeds the agent's approval threshold. Same offer fields plus:
{
"status": "pending_approval",
"reason": "Amount $7.50 exceeds approval threshold of $5",
"approval_threshold_usd": 5,
"rail": "...",
"amount_usd": 7.50,
"recipient": { "...": "..." }
}403 — denied
The cap, allowlist, or registry-as-trust-boundary check failed. Same offer fields plus:
{
"status": "denied",
"reason": "Spend cap exceeded for this period",
"rail": "...",
"amount_usd": 25.00,
"recipient": { "...": "..." }
}400 — bad request
| Reason | When |
|---|---|
url is required | Missing or empty url. |
Invalid URL | new URL(url) throws. |
URL is not allowed (private/loopback hosts blocked) | Host is localhost, 127.0.0.0/8, 169.254.0.0/16, 0.0.0.0, *.local, or *.internal. |
URL did not return a payment challenge (status N) | Probe got a non-402 response. |
URL returned 402 but no parseable x402 or MPP challenge. | 402 body / headers not in either format. |
Could not reach URL: ... | Network failure (DNS, TLS, timeout). |
Cannot price ..., Unsupported x402 network ..., MPP method ... not supported yet | Recognized challenge but Canopy can't price it. |
401 / 404 / 503
| Status | Meaning |
|---|---|
401 | Invalid / revoked API key. |
404 | Agent not found in this org. |
503 | Org treasury not provisioned. |
Example
curl --request POST \
--url https://trycanopy.ai/api/quote \
--header "Authorization: Bearer ak_live_xxxxxxxxxxxxxxxx" \
--header "Content-Type: application/json" \
--data '{
"url": "https://stableenrich.dev/api/apollo/people-enrich",
"agent_id": "agt_xxxxxxxx"
}'A 200 response means the policy would have allowed the call had you signed it. Treasury balance is not checked by
quote—canopy.fetch()(or/api/sign) is where the rail funding is verified at sign time.