{
  "info": {
    "_postman_id": "8c1f0a52-3d9e-4b7a-9c61-2f4e5d6a7b80",
    "name": "Caravel Pay API",
    "description": "Caravel Pay creates hosted card, Apple Pay and Google Pay checkouts and pays out to your Polygon wallet, minus the 2% Caravel fee. Your buyer pays the order total; most card providers keep their own fee out of it, and the Caravel fee is 2% of the order total. There are no accounts or dashboards: \"Connect\" returns a merchant id and secret, and every other request is signed with them.\n\n1. Set `site_url` (your HTTPS site) and `payout_address` (your Polygon wallet) in the collection variables.\n2. Run **Connect: get your API keys** once. Its test script stores `merchant_id` and `secret` in the collection variables. Every run creates a NEW merchant.\n3. Every other request is signed by the collection pre-request script: `Caravel-Signature` = lowercase hex HMAC-SHA256(secret, `timestamp\\nMETHOD\\npath+query\\nrawBody`).\n\nKeep the secret out of shared workspaces: store it as a secret-type or current-value-only variable. Docs: https://caravelpay.app/docs/ · Reference for AI coding agents: caravel-pay-api-llm.md",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Caravel Pay request signing (docs: Authentication and request signing).",
          "// signature = lowercase hex HMAC-SHA256(secret as ASCII, timestamp + \"\\n\" + METHOD + \"\\n\" + path + \"\\n\" + rawBody)",
          "// path = the request path plus its query string, if any (never the full URL); rawBody = \"\" for GET.",
          "(function () {",
          "    var resolved = pm.variables.replaceIn(pm.request.url.toString());",
          "    var match = /^[a-z][a-z0-9+.-]*:\\/\\/[^\\/?#]+([^#]*)/i.exec(resolved);",
          "    var path = match ? match[1] : resolved;",
          "    if (path === '' || path.charAt(0) === '?') { path = '/' + path; }",
          "    path = path.replace(/\\?$/, '');",
          "    // Unsigned: POST /v1/connect, POST /v1/reports, and requests marked with a Caravel-Unsigned header",
          "    // (removed here, never sent), such as creating a payment link without API keys.",
          "    if (pm.request.headers.has('Caravel-Unsigned')) {",
          "        pm.request.headers.remove('Caravel-Unsigned');",
          "        return;",
          "    }",
          "    if (/^\\/v1\\/(connect|reports)(\\?|$)/.test(path)) {",
          "        return;",
          "    }",
          "    var merchant = pm.variables.get('merchant_id');",
          "    var secret = pm.variables.get('secret');",
          "    if (!merchant || !secret) {",
          "        throw new Error('Set the merchant_id and secret collection variables first (run \"Connect: get your API keys\").');",
          "    }",
          "    var method = pm.request.method.toUpperCase();",
          "    var body = '';",
          "    if (method !== 'GET' && pm.request.body && pm.request.body.mode === 'raw') {",
          "        body = pm.variables.replaceIn(pm.request.body.raw || '');",
          "    }",
          "    var timestamp = String(Math.floor(Date.now() / 1000));",
          "    var signature = CryptoJS.HmacSHA256(timestamp + '\\n' + method + '\\n' + path + '\\n' + body, secret).toString(CryptoJS.enc.Hex);",
          "    pm.request.headers.upsert({ key: 'Caravel-Merchant', value: merchant });",
          "    pm.request.headers.upsert({ key: 'Caravel-Timestamp', value: timestamp });",
          "    pm.request.headers.upsert({ key: 'Caravel-Signature', value: signature });",
          "})();"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.caravelpay.app",
      "type": "string",
      "description": "API origin. Production placeholder host until the final domain is announced."
    },
    {
      "key": "site_url",
      "value": "https://shop.example",
      "type": "string",
      "description": "Your HTTPS site. Return, cancel and callback URLs must be on it (and its www. twin)."
    },
    {
      "key": "payout_address",
      "value": "",
      "type": "string",
      "description": "Your Polygon wallet (0x + 40 hex). Every payout is sent here."
    },
    {
      "key": "merchant_id",
      "value": "",
      "type": "string",
      "description": "Set by Connect."
    },
    {
      "key": "secret",
      "value": "",
      "type": "secret",
      "description": "Set by Connect. Shown only once by the API: keep it private."
    },
    {
      "key": "order_id",
      "value": "test-1",
      "type": "string",
      "description": "Your order reference for Create a checkout; unique per merchant."
    },
    {
      "key": "checkout_id",
      "value": "",
      "type": "string",
      "description": "Set by Create a checkout."
    },
    {
      "key": "next_cursor",
      "value": "",
      "type": "string",
      "description": "Set by List checkouts."
    },
    {
      "key": "webhook_url",
      "value": "https://shop.example/webhooks/caravel",
      "type": "string",
      "description": "Your webhook endpoint, for the signed test webhook."
    },
    {
      "key": "link_id",
      "value": "",
      "type": "string",
      "description": "Set by Create a payment link (signed)."
    }
  ],
  "item": [
    {
      "name": "Connect: get your API keys",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.response.code >= 200 && pm.response.code < 300) {",
              "    var data = pm.response.json();",
              "    if (data.merchant_id !== undefined && data.merchant_id !== null) { pm.collectionVariables.set('merchant_id', data.merchant_id); }",
              "    if (data.secret !== undefined && data.secret !== null) { pm.collectionVariables.set('secret', data.secret); }",
              "}"
            ]
          }
        }
      ],
      "request": {
        "auth": {
          "type": "noauth"
        },
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"site_url\": \"{{site_url}}\",\n  \"payout_address\": \"{{payout_address}}\",\n  \"platform\": \"api\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/v1/connect",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "connect"
          ]
        },
        "description": "Unsigned. Creates a NEW merchant every time and returns its id and secret once. To change the wallet or rotate the secret, connect again and keep the old credentials until their open checkouts have finished. Limited to 10 per hour per IP. Optional fee_mode (\"seller\", \"split\" or \"buyer\") sets who pays the fees by default; leave it out and you pay both, as before."
      },
      "response": [
        {
          "name": "201 Connected",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"merchant_id\": \"mch_7Q2KZX4PRB\",\n  \"secret\": \"9b2e7c4a1f6d8e3b5a0c9d2f7e4b1a6c3d8f5e2b9a7c4d1e6f3b8a5c2d9e7f4a\",\n  \"status\": \"active\",\n  \"fee_rate\": \"2%\"\n}"
        },
        {
          "name": "409 Wallet limit",
          "status": "Conflict",
          "code": 409,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"limit_exceeded\",\n    \"message\": \"This payout wallet can't be connected to more merchants.\",\n    \"param\": \"payout_address\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Retrieve your merchant",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/v1/merchant",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "merchant"
          ]
        },
        "description": "The quickest way to test your credentials."
      },
      "response": [
        {
          "name": "200 Merchant",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"mch_7Q2KZX4PRB\",\n  \"site_url\": \"https://shop.example\",\n  \"origins\": [\n    \"https://shop.example\",\n    \"https://www.shop.example\"\n  ],\n  \"payout_address\": \"0x52908400098527886E0F7030069857D2E4169EE7\",\n  \"status\": \"active\",\n  \"fee_rate\": \"2%\",\n  \"fee_mode\": \"seller\"\n}"
        },
        {
          "name": "401 Bad signature",
          "status": "Unauthorized",
          "code": 401,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"invalid_signature\",\n    \"message\": \"Caravel-Signature doesn't match. Sign the exact path and body you send.\",\n    \"param\": \"Caravel-Signature\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Create a checkout",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.response.code >= 200 && pm.response.code < 300) {",
              "    var data = pm.response.json();",
              "    if (data.id !== undefined && data.id !== null) { pm.collectionVariables.set('checkout_id', data.id); }",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"order_id\": \"{{order_id}}\",\n  \"amount\": \"136.41\",\n  \"currency\": \"USD\",\n  \"customer_email\": \"buyer@example.com\",\n  \"return_url\": \"{{site_url}}/thanks\",\n  \"cancel_url\": \"{{site_url}}/cart\",\n  \"callback_url\": \"{{site_url}}/webhooks/caravel\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/v1/checkouts",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts"
          ]
        },
        "description": "amount is a string with exactly two decimals; currency is \"USD\" here (for euros, pounds, kronor and so on see \"Create a checkout in another currency\"). return_url, cancel_url and callback_url must be HTTPS on your merchant origins. The same order_id with the same details returns the same checkout (200); different details give 409 order_id_conflict. Checkouts expire after 60 minutes: retry with a new order_id such as 1042-2. Optional fee_mode says who pays the fees for this checkout (see the next request); without it your merchant default applies."
      },
      "response": [
        {
          "name": "201 Created",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_8Qe2TnV4kR\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_8Qe2TnV4kR\",\n  \"status\": \"awaiting_payment\",\n  \"amount\": \"136.41\",\n  \"currency\": \"USD\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\"\n}"
        },
        {
          "name": "409 order_id used with other details",
          "status": "Conflict",
          "code": 409,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"order_id_conflict\",\n    \"message\": \"This order_id already belongs to a checkout with different details. Use a new order_id.\",\n    \"param\": \"order_id\"\n  }\n}"
        },
        {
          "name": "400 Bad amount",
          "status": "Bad Request",
          "code": 400,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"invalid_amount\",\n    \"message\": \"amount must be a string with two decimals, like \\\"136.41\\\"\",\n    \"param\": \"amount\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Create a checkout: your buyer pays the fees",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"order_id\": \"{{order_id}}-fees\",\n  \"amount\": \"100.00\",\n  \"currency\": \"USD\",\n  \"customer_email\": \"buyer@example.com\",\n  \"return_url\": \"{{site_url}}/thanks\",\n  \"cancel_url\": \"{{site_url}}/cart\",\n  \"callback_url\": \"{{site_url}}/webhooks/caravel\",\n  \"fee_mode\": \"buyer\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/v1/checkouts",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts"
          ]
        },
        "description": "fee_mode says who pays the fees: \"seller\" (default: your buyer pays the order total, both fees come off your payout), \"split\" (the card provider adds its fee to your buyer's total; you pay the 2% Caravel fee) or \"buyer\" (the 2% Caravel fee is added to your buyer's total as well, so you receive the full order total). amount stays your own order total. A split or buyer checkout also returns fee_mode and buyer_total_estimate (what your buyer is charged before the card provider's fee); a seller checkout returns neither. The same order_id with another fee_mode gives 409 order_id_conflict. Card surcharges are restricted in some countries (for example on consumer cards in the EU and the UK): check your local rules first."
      },
      "response": [
        {
          "name": "201 Created (buyer pays both fees)",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_4Hn7WqB2mZ\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_4Hn7WqB2mZ\",\n  \"status\": \"awaiting_payment\",\n  \"amount\": \"100.00\",\n  \"currency\": \"USD\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\",\n  \"fee_mode\": \"buyer\",\n  \"buyer_total_estimate\": \"102.04\"\n}"
        },
        {
          "name": "201 Created (split)",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_6Tz3KpD9vX\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_6Tz3KpD9vX\",\n  \"status\": \"awaiting_payment\",\n  \"amount\": \"100.00\",\n  \"currency\": \"USD\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\",\n  \"fee_mode\": \"split\",\n  \"buyer_total_estimate\": \"100.00\"\n}"
        },
        {
          "name": "409 order_id used with another fee_mode",
          "status": "Conflict",
          "code": 409,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"order_id_conflict\",\n    \"message\": \"This order_id already belongs to a checkout with different details. Use a new order_id.\",\n    \"param\": \"order_id\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Create a checkout in another currency",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"order_id\": \"{{order_id}}-eur\",\n  \"amount\": \"45.00\",\n  \"currency\": \"EUR\",\n  \"customer_email\": \"buyer@example.com\",\n  \"return_url\": \"{{site_url}}/thanks\",\n  \"cancel_url\": \"{{site_url}}/cart\",\n  \"callback_url\": \"{{site_url}}/webhooks/caravel\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/v1/checkouts",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts"
          ]
        },
        "description": "Send amount and currency as your store has them: EUR, GBP, CAD, AUD, NZD, SEK, NOK, DKK, CHF, PLN, CZK, HUF, RON, HKD, SGD, MXN, BRL, ZAR, ILS, INR, THB, MYR, PHP, TRY (two decimals), JPY or KRW (no decimals: \"5000\"). The amount is converted to US dollars once, when the checkout is created, and fixed for that checkout: your buyer is charged in US dollars and you are paid in USDC. The checkout keeps your amount and currency and adds usd_amount (the order total in US dollars, rounded half-up to the cent) and fx_rate (US dollars for one unit). Fees, the 1.00 minimum, paid_amount and the payout are in US dollars. Optional fx_buffer_bps (0 to 500) adds your own margin to the converted amount. Decide that an order is paid from status, never by comparing usd_amount with a total in your own currency. When no exchange rate can be confirmed the answer is 503 service_unavailable and nothing is created: retry after Retry-After seconds."
      },
      "response": [
        {
          "name": "201 Created (euros)",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_2Vd8XsQ5nL\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_2Vd8XsQ5nL\",\n  \"status\": \"awaiting_payment\",\n  \"amount\": \"45.00\",\n  \"currency\": \"EUR\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\",\n  \"usd_amount\": \"48.60\",\n  \"fx_rate\": \"1.08\"\n}"
        },
        {
          "name": "201 Created (yen, no decimals)",
          "status": "Created",
          "code": 201,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_9Jw4RtE7cA\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_9Jw4RtE7cA\",\n  \"status\": \"awaiting_payment\",\n  \"amount\": \"5000\",\n  \"currency\": \"JPY\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\",\n  \"usd_amount\": \"31.61\",\n  \"fx_rate\": \"0.0063224015\"\n}"
        },
        {
          "name": "400 Currency not supported",
          "status": "Bad Request",
          "code": 400,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"unsupported_currency\",\n    \"message\": \"currency must be one of: USD, EUR, GBP, CAD, AUD, NZD, SEK, NOK, DKK, CHF, PLN, CZK, HUF, RON, HKD, SGD, MXN, BRL, ZAR, ILS, INR, THB, MYR, PHP, TRY, JPY, KRW.\",\n    \"param\": \"currency\"\n  }\n}"
        },
        {
          "name": "503 No confirmed exchange rate",
          "status": "Service Unavailable",
          "code": 503,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"service_unavailable\",\n    \"message\": \"Exchange rates for EUR are temporarily unavailable. Nothing was created; retry shortly.\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Quote a conversion to US dollars",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/v1/fx-quote?currency=EUR&amount=45.00",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "fx-quote"
          ],
          "query": [
            {
              "key": "currency",
              "value": "EUR"
            },
            {
              "key": "amount",
              "value": "45.00"
            }
          ]
        },
        "description": "Today's indicative conversion, for telling your buyer \"You'll be charged in US dollars: about $48.60\" before you redirect them. Not a promise: the rate is fixed when the checkout is created. amount is optional (without it you get the rate only); fx_buffer_bps works as on a checkout. Cache the answer for a few minutes. The query string is part of the signed path."
      },
      "response": [
        {
          "name": "200 Quote",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"currency\": \"EUR\",\n  \"amount\": \"45.00\",\n  \"fx_rate\": \"1.08\",\n  \"usd_amount\": \"48.60\",\n  \"quoted_at\": \"2026-11-09T14:04:12Z\"\n}"
        },
        {
          "name": "400 Currency not supported",
          "status": "Bad Request",
          "code": 400,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"unsupported_currency\",\n    \"message\": \"currency must be one of: EUR, GBP, CAD, AUD, NZD, SEK, NOK, DKK, CHF, PLN, CZK, HUF, RON, HKD, SGD, MXN, BRL, ZAR, ILS, INR, THB, MYR, PHP, TRY, JPY, KRW.\",\n    \"param\": \"currency\"\n  }\n}"
        },
        {
          "name": "503 No confirmed exchange rate",
          "status": "Service Unavailable",
          "code": 503,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"service_unavailable\",\n    \"message\": \"Exchange rates for EUR are temporarily unavailable. Nothing was created; retry shortly.\"\n  }\n}"
        }
      ]
    },
    {
      "name": "Retrieve a checkout",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/v1/checkouts/{{checkout_id}}",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts",
            "{{checkout_id}}"
          ]
        },
        "description": "The current status, paid_amount and, once paid, the payout. Decide whether an order is paid from this response, not from a webhook body."
      },
      "response": [
        {
          "name": "200 Paid",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"chk_8Qe2TnV4kR\",\n  \"checkout_url\": \"https://pay.caravelpay.app/c/chk_8Qe2TnV4kR\",\n  \"status\": \"paid\",\n  \"amount\": \"136.41\",\n  \"currency\": \"USD\",\n  \"expires_at\": \"2026-11-09T15:04:12Z\",\n  \"order_id\": \"1042\",\n  \"created_at\": \"2026-11-09T14:04:12Z\",\n  \"paid_amount\": \"136.41\",\n  \"payout\": {\n    \"amount\": \"133.68\",\n    \"asset\": \"USDC\",\n    \"tx_hash\": \"0x5d1e8c0b7a4f2e19c6d3b8a0f5e7c2d1a9b4e6f8c0d2a7b5e3f1c9d8a6b4a07b\"\n  }\n}"
        },
        {
          "name": "404 Not yours",
          "status": "Not Found",
          "code": 404,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"error\": {\n    \"code\": \"not_found\",\n    \"message\": \"No checkout with that id belongs to this merchant.\",\n    \"param\": \"id\"\n  }\n}"
        }
      ]
    },
    {
      "name": "List checkouts",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.response.code >= 200 && pm.response.code < 300) {",
              "    var data = pm.response.json();",
              "    if (data.next_cursor !== undefined && data.next_cursor !== null) { pm.collectionVariables.set('next_cursor', data.next_cursor); }",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/v1/checkouts?limit=20",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts"
          ],
          "query": [
            {
              "key": "limit",
              "value": "20"
            }
          ]
        },
        "description": "Newest first. The query string is part of the signed path."
      },
      "response": [
        {
          "name": "200 Page",
          "status": "OK",
          "code": 200,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"data\": [\n    {\n      \"id\": \"chk_8Qe2TnV4kR\",\n      \"checkout_url\": \"https://pay.caravelpay.app/c/chk_8Qe2TnV4kR\",\n      \"status\": \"paid\",\n      \"amount\": \"136.41\",\n      \"currency\": \"USD\",\n      \"expires_at\": \"2026-11-09T15:04:12Z\",\n      \"order_id\": \"1042\",\n      \"created_at\": \"2026-11-09T14:04:12Z\",\n      \"paid_amount\": \"136.41\",\n      \"payout\": {\n        \"amount\": \"133.68\",\n        \"asset\": \"USDC\",\n        \"tx_hash\": \"0x5d1e8c0b7a4f2e19c6d3b8a0f5e7c2d1a9b4e6f8c0d2a7b5e3f1c9d8a6b4a07b\"\n      }\n    }\n  ],\n  \"next_cursor\": null\n}"
        }
      ]
    },
    {
      "name": "List checkouts: next page",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{base_url}}/v1/checkouts?limit=20&cursor={{next_cursor}}",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "checkouts"
          ],
          "query": [
            {
              "key": "limit",
              "value": "20"
            },
            {
              "key": "cursor",
              "value": "{{next_cursor}}"
            }
          ]
        },
        "description": "Pass next_cursor from the previous page as cursor; it is null on the last page."
      },
      "response": []
    },
    {
      "name": "Payment links",
      "item": [
        {
          "name": "Create a payment link without API keys",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Caravel-Unsigned",
                "value": "1",
                "description": "Tells the collection script not to sign this request; removed before sending."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payout_address\": \"{{payout_address}}\",\n  \"amount\": \"25.00\",\n  \"currency\": \"USD\",\n  \"description\": \"Logo design, 2 rounds\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/payment-links",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "payment-links"
              ]
            },
            "description": "No signature: send payout_address. The answer is only id and url; no credentials are returned and these links send no webhooks. amount null lets the buyer enter it (min_amount/max_amount). 30 links an hour per address. Optional fee_mode (\"seller\", \"split\" or \"buyer\") says who pays the fees. currency can be any supported currency (\"EUR\", \"GBP\", …): the price is converted to US dollars each time the link is opened."
          },
          "response": [
            {
              "name": "201 Created",
              "status": "Created",
              "code": 201,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "_postman_previewlanguage": "json",
              "body": "{\n  \"id\": \"lnk_K7Q2ZX4PRBM5TW3NHJ6D\",\n  \"url\": \"https://pay.caravelpay.app/l/lnk_K7Q2ZX4PRBM5TW3NHJ6D\"\n}"
            }
          ]
        },
        {
          "name": "Create a payment link (signed)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code >= 200 && pm.response.code < 300) {",
                  "    var data = pm.response.json();",
                  "    if (data.id !== undefined && data.id !== null) { pm.collectionVariables.set('link_id', data.id); }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"49.00\",\n  \"currency\": \"USD\",\n  \"description\": \"Workshop seat\",\n  \"reusable\": false,\n  \"callback_url\": \"{{site_url}}/webhooks/caravel\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/payment-links",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "payment-links"
              ]
            },
            "description": "Signed: the link belongs to your merchant and pays out to its wallet; its checkouts appear in GET /v1/checkouts and send webhooks to callback_url. Don't send payout_address. Optional fee_mode (\"seller\", \"split\" or \"buyer\") says who pays the fees for this link; without it your merchant default applies."
          },
          "response": []
        },
        {
          "name": "Retrieve a payment link",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/payment-links/{{link_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "payment-links",
                "{{link_id}}"
              ]
            },
            "description": "Signed. Status (active, completed, deactivated) and how many of its checkouts are paid."
          },
          "response": []
        },
        {
          "name": "Deactivate a payment link",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/payment-links/{{link_id}}/deactivate",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "payment-links",
                "{{link_id}}",
                "deactivate"
              ]
            },
            "description": "Signed. New checkouts stop at once; open ones can still be paid. Permanent."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Report a seller",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"url\": \"https://shop.example/product/123\",\n  \"category\": \"scam_fraud\",\n  \"details\": \"Describe what is wrong. No card numbers or passwords.\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/v1/reports",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "v1",
            "reports"
          ]
        },
        "description": "Unsigned. Reports a site or checkout that sells something illegal, defrauds buyers or impersonates someone. category: illegal_goods, scam_fraud, impersonation or other. 5 reports an hour per address."
      },
      "response": [
        {
          "name": "202 Received",
          "status": "Accepted",
          "code": 202,
          "header": [
            {
              "key": "Content-Type",
              "value": "application/json"
            }
          ],
          "_postman_previewlanguage": "json",
          "body": "{\n  \"id\": \"rpt_K7Q2ZX4PRBM3N5T6\"\n}"
        }
      ]
    },
    {
      "name": "Send a signed test webhook to your endpoint",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Caravel-Event",
            "value": "evt_TEST{{$timestamp}}"
          },
          {
            "key": "User-Agent",
            "value": "CaravelPay-Webhooks/1"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"id\": \"evt_TEST{{$timestamp}}\",\n  \"type\": \"checkout.paid\",\n  \"created\": \"2026-11-09T14:09:37Z\",\n  \"data\": {\n    \"id\": \"chk_8Qe2TnV4kR\",\n    \"checkout_url\": \"https://pay.caravelpay.app/c/chk_8Qe2TnV4kR\",\n    \"status\": \"paid\",\n    \"amount\": \"136.41\",\n    \"currency\": \"USD\",\n    \"expires_at\": \"2026-11-09T15:04:12Z\",\n    \"order_id\": \"1042\",\n    \"created_at\": \"2026-11-09T14:04:12Z\",\n    \"paid_amount\": \"136.41\",\n    \"payout\": {\n      \"amount\": \"133.68\",\n      \"asset\": \"USDC\",\n      \"tx_hash\": \"0x5d1e8c0b7a4f2e19c6d3b8a0f5e7c2d1a9b4e6f8c0d2a7b5e3f1c9d8a6b4a07b\"\n    }\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{webhook_url}}"
        },
        "description": "Posts a checkout.paid event to your own webhook_url, signed with your secret over the path of webhook_url, the way Caravel Pay does. Use it to test your verification code. A correct handler re-fetches the checkout with GET /v1/checkouts/{id} and, because this checkout id is only an example, must NOT mark anything paid."
      },
      "response": []
    }
  ]
}
