POST /api/sign
Most callers should use
canopy.pay()instead. The SDKs handle authentication, error classification, retries, and idempotency for you. This page is for SDK builders and developers calling the wire protocol directly.
POST /api/sign is the core endpoint. It evaluates your agent's policy atomically — checking the recipient allowlist, spend cap, and approval threshold — then either signs and submits the transaction, queues it for human approval, or rejects it.
Base URL: https://trycanopy.ai
Request
POST /api/sign
Authorization: Bearer <apiKey>
Content-Type: application/json
Idempotency-Key: <optional>Headers
| Header | Type | Description |
|---|---|---|
Authorization | string | Bearer ak_live_… or Bearer ak_test_… |
Idempotency-Key | string | Optional. Stable replay key. A repeat request with the same (agent_id, Idempotency-Key) returns the cached decision without re-charging. |
Body
| Field | Type | Default | Description |
|---|---|---|---|
agent_id | string | required | The agent ID (agt_…) making the payment |
type | string | "raw_transaction" | eip3009, permit2, raw_transaction, x402, or mpp |
chain_id | integer | 8453 | Chain ID; defaults to Base mainnet |
recipient_address | string | required for direct types | Final 0x recipient. For x402 and mpp, the server re-derives this from the runtime 402 envelope; the field is optional and ignored on those types. |
amount_usd | number | required for direct types | USD amount, e.g. 0.10. For x402/mpp, server-derived from the offer/challenge. |
payload | object | — | Wire-level transaction payload (shape depends on type). For x402 and mpp, include resource_url so the server can attribute the call to a registered service. |
dry_run | boolean | false | If true, evaluate policy and return outcome without signing or persisting |
200 — allowed
Payment passed policy and was signed and submitted.
{
"signature": null,
"tx_hash": "0xabc123...",
"agent_id": "agt_xxxxxxxx",
"cost_usd": "0.10",
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"idempotent": false
}| Field | Type | Description |
|---|---|---|
signature | string | null | EIP-3009 / permit2 signature, when applicable |
tx_hash | string | null | On-chain transaction hash, or null if submission is async |
agent_id | string | Echoes the request |
cost_usd | string | null | Actual USD cost |
transaction_id | string | UUID for the transaction record |
idempotent | boolean | true on cached replay |
202 — pending approval
Amount exceeds the agent's approval threshold. A human must approve in the dashboard.
{
"status": "pending_approval",
"reason": "Amount $7.50 exceeds approval threshold of $5",
"transaction_id": "550e8400-...",
"approval_request_id": "550e8400-..."
}Pass approval_request_id to GET /api/approvals/{id}/status.
403 — policy denied
Recipient not on allowlist, spend cap exceeded, or other policy-level rejection.
{
"error": "Policy denied",
"reason": "Spend cap exceeded: $8.00 + $5.00 > $10 / 24h",
"transaction_id": "550e8400-..."
}401 — invalid API key
The Authorization header is missing, malformed, or the key has been revoked.
Example
curl --request POST \
--url https://trycanopy.ai/api/sign \
--header "Authorization: Bearer ak_live_xxxxxxxxxxxxxxxx" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order-abc-123" \
--data '{
"agent_id": "agt_xxxxxxxx",
"type": "raw_transaction",
"chain_id": 8453,
"recipient_address": "0x1234567890abcdef1234567890abcdef12345678",
"amount_usd": 0.10
}'