Operations
Rendered at build time from openapi/canopy-v1.json, which is generated from the route table the server enforces. This page cannot describe a route that is not wired.
GET /api/v1/intents
listPaymentIntents
List payment intents
Returns the merchant's payment intents in reverse-chronological order, paginated by an opaque cursor over a monotonic sequence -- pass the last-returned intent's cursor to continue. Each entry reflects the intent's current state as of this call; to poll a single intent instead, use GET /api/v1/intents/{id}.
- Required scope:
payment_intents:create - Rate limit: 60 requests / minute
- Statuses:
200,400,401,403,404,413,429,500
curl "https://www.canopypay.io/api/v1/intents?limit=20&cursor=1042" \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01"{
"object": "list",
"data": [
{
"intentId": "6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e",
"inboxAddress": "0x9f2a1c7b3e5d8a604c1f2b9e7d3a5c8b6e4f1d20",
"created": false
}
],
"has_more": true,
"next_cursor": "1062"
}POST /api/v1/intents
createPaymentIntent
Create a payment intent
Creates a payment intent and its deposit inbox address. An intent may be open-ended or carry optional priceUnits and oneTime terms. It names a deposit address a payer can fund by connected wallet or manual transfer; Canopy detects the deposit independently and settles net of the platform fee. Every successful response includes a fresh widgetToken, which opens the intent's own first-party checkout surface. To mount checkout in a browser, hand the intent's intentId to the SDK's mount() -- there is no second server-to-server call and no short-lived secret for you to hold or forward. Name the destination with payoutWallet, payoutNamespace, payoutChainReference and payoutTokenAddress. Omitted chain fields inherit the account defaults; the token inherits the matching account currency or the configured destination-chain default. Only configured live chain assets are admitted. The token and wallet are pinned to the intent, including retries. Dynamic origin routing must be provisioned to collect the source fee and deliver to that destination. Only fulfill intents your own backend created -- treat unknown intent IDs as unpaid. There is exactly one dedupe rule: at most one ACTIVE intent may exist per (merchant account, payout namespace, chain, wallet, token). A create naming a wallet that already has an active intent with the same terms returns THAT intent with created:false and a fresh widgetToken (the prior token stops authenticating); the same resolved destination with different priceUnits or oneTime is a 409 that writes nothing. A create with no destination fields always mints a new intent and a new deposit address. A different chain or token is a different destination. Supplying any destination field enables dedupe; omitted fields resolve from account defaults. Once an intent leaves the active state, a later create may use that destination again. Repeatable intents stay active after deposits. Do not blindly retry destination-less creates or use merchantReference as a dedupe key. EVM addresses are compared case-insensitively; Solana addresses are compared byte-exactly. A one-time intent (oneTime:true, which requires priceUnits) closes itself -- state becomes settled -- once the customer's counted deposits reach the price, freeing its payout wallet for the merchant's next sale.
- Required scope:
payment_intents:create - Rate limit: 30 requests / minute
- Statuses:
201,400,401,403,404,409,413,429,500
curl -X POST https://www.canopypay.io/api/v1/intents \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01" \
-H "Content-Type: application/json" \
-d '{
"merchantReference": "order_10024",
"metadata": { "userId": "u_7781" }
}'{
"intentId": "6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e",
"inboxAddress": "0x9f2a1c7b3e5d8a604c1f2b9e7d3a5c8b6e4f1d20",
"created": true,
"priceUnits": null,
"oneTime": false,
"widgetToken": "2f7a1d9c4b6e8035a1c7d3f6b8e04a2c5d7f9b1e3a6c8d0f2b4e6a8c0d2f4b6e"
}GET /api/v1/intents/{id}
getPaymentIntent
Retrieve a payment intent
Fetches one payment intent by id. Returns 404 both when the id does not exist and when it belongs to a different merchant -- cross-merchant reads are never distinguished from not-found, so a 404 here never confirms another merchant's intent exists.
- Required scope:
payment_intents:create - Rate limit: 120 requests / minute
- Statuses:
200,400,401,403,404,413,429,500
curl https://www.canopypay.io/api/v1/intents/6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01"{
"intentId": "6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e",
"inboxAddress": "0x9f2a1c7b3e5d8a604c1f2b9e7d3a5c8b6e4f1d20",
"created": false
}GET /api/v1/intents/{id}/origins
listIntentOrigins
List an intent's source origins
Returns every live source origin for the intent's account, each with a per-origin depositIssuable flag, plus the settle rails, plus an intent-level sourceDepositIssuable flag. Origins are returned EVEN WHEN sourceDepositIssuable is false, so a caller can render the full origin list and explain the refusal rather than showing an empty picker. Returns 404 for an archived intent, and for another merchant's intent -- the two are never distinguished.
- Required scope:
payment_intents:create - Rate limit: 120 requests / minute
- Statuses:
200,400,401,403,404,413,429,500
curl https://www.canopypay.io/api/v1/intents/6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e/origins \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01"{
"origins": [
{
"namespace": "eip155",
"reference": "8453",
"chainId": 8453,
"name": "Base",
"shortName": "Base",
"publicRpcUrl": "https://mainnet.base.org",
"explorerUrl": "https://basescan.org",
"nativeName": "Ether",
"nativeSymbol": "ETH",
"nativeDecimals": 18,
"token": {
"symbol": "USDC",
"address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"decimals": 6
},
"caip19": "eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"minimumUnits": "2082500",
"minimumDisplay": "2.0825",
"depositIssuable": true
}
],
"settleRails": [
{
"id": "robinhood-mainnet:usdg",
"chainId": 4663,
"chainName": "Robinhood Chain",
"token": {
"symbol": "USDG",
"address": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
"decimals": 6
},
"explorerUrl": "https://robinhoodchain.blockscout.com",
"status": "live",
"default": true
}
],
"sourceDepositIssuable": true
}POST /api/v1/intents/{id}/source-inbox
createIntentSourceInbox
Issue a source deposit inbox for an intent
Issues (or returns) the deposit inbox address for one source origin on the intent's bound customer. Takes either { chainId } for an EVM origin or { namespace, reference, mint } for Solana. Needs no widget and no customer-session cookie -- this is a headless, server-to-server call. Safe to repeat: a repeat call returns the same address, never a second one. On failure, returns one of four named error codes: source_inbox_unavailable (503, retry after Retry-After -- the gate is temporarily unreachable), source_inbox_refused (500, not retryable -- contact Canopy support), source_inbox_not_active (503, not retryable -- issuance is disabled for this origin, pick a different one), or customer_not_bound (503, retry after Retry-After -- the intent's deposit customer is not bound yet).
- Required scope:
payment_intents:create - Rate limit: 30 requests / minute
- Statuses:
201,400,401,403,404,413,429,500,503
curl -X POST https://www.canopypay.io/api/v1/intents/6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e/source-inbox \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01" \
-H "Content-Type: application/json" \
-d '{ "chainId": 8453 }'{
"address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"caip10": "eip155:8453:0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"
}POST /api/v1/intents/{id}/view
recordIntentView
Report that an intent is on screen
Call it when you display a deposit address to the payer, and again whenever the payer picks a chain or token. The optional selection field points Canopy's deposit scanning at that one token's address -- the fastest way to detect a deposit before webhooks are live on a fresh address. Without it, Canopy watches all of the customer's addresses, which is slower. It is a detection hint only: it never settles a payment or moves funds. recorded: false means nothing matched the selection -- usually the source inbox for that chain has not been issued yet. Returns 404 for an unknown, archived, or another merchant's intent alike. Limited to 30 calls per minute per intent.
- Required scope:
payment_intents:create - Rate limit: 60 requests / minute
- Statuses:
202,400,401,403,404,413,429,500
curl -X POST https://www.canopypay.io/api/v1/intents/6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e/view \
-H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
-H "Canopy-Version: 2026-09-01" \
-H "Content-Type: application/json" \
-d '{ "selection": { "namespace": "eip155", "reference": "8453", "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" } }'{
"recorded": true
}