CanopyAPI docs

Guides

Source deposit inboxes

Your user holds funds on a chain other than the settle chain, and you want a deposit address for that chain without mounting the widget.

When you need this

GET /api/v1/intents/{id}/origins and POST /api/v1/intents/{id}/source-inbox are a headless, server-to-server pair: no widget, and no customer-session cookie. If your integration already mounts the checkout widget, it never needs to call either of these -- see the quickstart for the widget path instead.

List the origins you can offer

GET /api/v1/intents/{id}/origins returns every live source origin for the intent's account, each carrying its own depositIssuable flag, alongside settleRails and an intent-level sourceDepositIssuable flag. Origins are returned even when sourceDepositIssuable is false, so a caller renders the full list and explains the refusal instead of showing an empty picker.

This determines how you build your picker: never hide the list because the intent-level flag is false.

The route returns 404 both for an archived intent and for another merchant's intent, and the two are never distinguished.

Requestbash
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"
200 OKjson
{
  "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
}

Issue an inbox for one origin

POST /api/v1/intents/{id}/source-inbox issues, or returns, the deposit inbox address for one source origin on the intent's bound customer. The body has two arms: { chainId } for an EVM origin, or { namespace, reference, mint } for Solana.

A repeat call returns the same address and never a second one, so the call is safe to retry. The 201 response carries address and caip10.

Requestbash
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 }'
201 Createdjson
{
  "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
  "caip10": "eip155:8453:0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"
}

When issuance is refused

Two of the four refusals are 503 and retryable, and two are not, and the split does not follow the status code.

CodeStatusRetryWhat to do
source_inbox_unavailable503Yes, after Retry-AfterThe gate is temporarily unreachable
source_inbox_refused500NoContact Canopy support
source_inbox_not_active503NoIssuance is disabled for this origin; offer a different one
customer_not_bound503Yes, after Retry-AfterThe intent's deposit customer is not bound yet

See also

  • Quickstart: hands your user a deposit address the widget way, at its hand-your-user-a-deposit-address step.
  • Payment intents: the full create-body contract this guide's intent already exists under.
  • API reference: both operations, with their full request and response shapes.