Canopy

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

HeaderTypeDescription
AuthorizationstringBearer ak_live_… or Bearer ak_test_…
Idempotency-KeystringOptional. Stable replay key. A repeat request with the same (agent_id, Idempotency-Key) returns the cached decision without re-charging.

Body

FieldTypeDefaultDescription
agent_idstringrequiredThe agent ID (agt_…) making the payment
typestring"raw_transaction"eip3009, permit2, raw_transaction, x402, or mpp
chain_idinteger8453Chain ID; defaults to Base mainnet
recipient_addressstringrequired for direct typesFinal 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_usdnumberrequired for direct typesUSD amount, e.g. 0.10. For x402/mpp, server-derived from the offer/challenge.
payloadobjectWire-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_runbooleanfalseIf 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
}
FieldTypeDescription
signaturestring | nullEIP-3009 / permit2 signature, when applicable
tx_hashstring | nullOn-chain transaction hash, or null if submission is async
agent_idstringEchoes the request
cost_usdstring | nullActual USD cost
transaction_idstringUUID for the transaction record
idempotentbooleantrue 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
  }'