API reference
Partners
A partner connects stores to Caravel Pay and adds its own fee, up to 12%, on top of the 2% Caravel fee. The fee is frozen on each store it connects and paid on-chain to the partner's own Polygon wallet with every paid order. How the program works and the partner rules are on Become a partner; this page is the API.
Sign up#
/v1/partnersUnsigned, and open to anyone: it creates your partner at once, with no review. The sign-up page makes this call for you.
| Field | Type | Description |
|---|---|---|
payout_address | string | Your Polygon wallet. Every fee you earn is sent here. It gets the same checks as a store's payout wallet: a wallet you control, not a contract, not an exchange deposit address, not on a sanctions or deny list. Fixed for this partner. |
label | string | Your business or brand name, 2 to 80 printable ASCII characters. Only Caravel Pay sees it. |
max_fee_bps | integer | The highest fee you can set for a store, in basis points (150 = 1.5%), from 1 to 1200 (12%). Fixed for this partner. |
contact_email | string | Where Caravel Pay can reach you. Never shown to stores or buyers. |
accept_terms | string | The version of the partner rules you accept: "2026-11-15". |
curl https://api.caravelpay.app/v1/partners \
-H "Content-Type: application/json" \
-d '{"payout_address":"0x1111111111111111111111111111111111111111","label":"Example Payments","max_fee_bps":500,"contact_email":"ops@example.com","accept_terms":"2026-11-15"}'{
"partner_id": "ptn_K4M2Q7XZ3A",
"secret": "9b2e…7f4a",
"status": "active",
"payout_address": "0x1111111111111111111111111111111111111111",
"max_fee_rate": "5%",
"max_fee_bps": 500,
"terms_version": "2026-11-15",
"created": "2026-11-15T10:00:00.000Z"
}
- The secret is shown once, in this answer only. Keep it on your server like a password. It signs your requests and makes your partner codes.
- Limits: a few sign-ups per address per hour (429 with
Retry-After), at most 3 partners per wallet and per contact email (409limit_exceeded), and a daily total (503). - A refused wallet answers 400 with
parampayout_address; the message doesn't say which check refused it.
Signing partner requests#
Partner requests are signed exactly like merchant requests, with Caravel-Partner instead of Caravel-Merchant and your partner secret as the key:
Caravel-Partner: ptn_K4M2Q7XZ3A
Caravel-Timestamp: 1767225600
Caravel-Signature: hex HMAC-SHA256 of "1767225600\nPOST\n/v1/connect\n" + body, keyed with your partner secret
The timestamp is unix seconds and must be within 300 seconds of our clock. Sign the exact path (with its query string) and the exact body you send; a GET or DELETE signs an empty body.
Connect a store from your server#
Call POST /v1/connect with the store's site and payout wallet, add partner_fee_bps, and sign the request with your partner credentials.
{"site_url": "https://shop.example", "payout_address": "0x2222222222222222222222222222222222222222", "platform": "api", "partner_fee_bps": 150}
{"merchant_id": "mch_7Q2KZX4PRB", "secret": "c41d…09be", "status": "active",
"fee_rate": "2%", "partner_id": "ptn_K4M2Q7XZ3A", "partner_fee_rate": "1.5%", "total_fee_rate": "3.5%"}
- The merchant secret is the store's. Use it to create the store's checkouts, or hand it to the store's own integration. Checkouts, webhooks and payouts then work as for any merchant.
- The fee is frozen on that merchant. To change it, connect again: that creates a new merchant id. Checkouts already open keep their fee.
fee_modecan besellerorsplitfor stores you connect;buyerisn't available for them.- At most 50 stores per partner in any 24 hours (409
limit_exceeded), plus the usual connect limits.
Partner codes#
A partner code lets a store's own integration connect through you without your secret. It names you, your fee for the stores that use it and an expiry, signed with your secret. You make codes yourself: nothing is stored on our side, and any number of stores can use a code until it expires.
Format: ptc_ + your partner id without ptn_ + _ + fee in basis points + _ + expiry (unix seconds) + _ + the first 32 hex characters of HMAC-SHA256 over "CARAVEL-PARTNER-CODE-V1\n" + partner_id + "\n" + fee + "\n" + expiry, keyed with your partner secret. The expiry may be at most 366 days ahead; the fee at most your maximum.
const crypto = require("crypto");
const id = "ptn_K4M2Q7XZ3A", secret = process.env.CARAVEL_PARTNER_SECRET;
const fee = 150, expires = Math.floor(Date.now() / 1000) + 90 * 86400; // 1.5%, 90 days
const sig = crypto.createHmac("sha256", secret).update(`CARAVEL-PARTNER-CODE-V1\n${id}\n${fee}\n${expires}`).digest("hex").slice(0, 32);
console.log(`ptc_${id.slice(4)}_${fee}_${expires}_${sig}`);import hashlib, hmac, os, time
pid, secret = "ptn_K4M2Q7XZ3A", os.environ["CARAVEL_PARTNER_SECRET"]
fee, expires = 150, int(time.time()) + 90 * 86400 # 1.5%, 90 days
sig = hmac.new(secret.encode(), f"CARAVEL-PARTNER-CODE-V1\n{pid}\n{fee}\n{expires}".encode(), hashlib.sha256).hexdigest()[:32]
print(f"ptc_{pid[4:]}_{fee}_{expires}_{sig}")The sign-up page also makes codes, in your browser. The store's integration sends the code as partner_code in its unsigned POST /v1/connect:
{"site_url": "https://shop.example", "payout_address": "0x2222222222222222222222222222222222222222", "platform": "api", "partner_code": "ptc_K4M2Q7XZ3A_150_1775001600_3f9c0d5e8a1b27c46d0e9f8a7b6c5d4e"}
The answer is the same as a signed connect: the store's own merchant id and secret, with partner_id, partner_fee_rate and total_fee_rate. A changed, expired or unknown code answers 400 with param partner_code; a paused partner answers 403 partner_paused. The Caravel Pay WooCommerce plugin doesn't send partner codes.
Your brand on the checkout#
/v1/partner/brandSigned. Every checkout of a store you connected then shows your name, logo and colour instead of Caravel Pay's, and texts that name Caravel Pay name you, in every language of the checkout. The amount, the payment methods, the payouts and the fees don't change, and the "Report this seller" link stays. The checkout runs at pay.caravelpay.app.
| Field | Type | Description |
|---|---|---|
name | string | The name buyers see, 1 to 40 characters. |
accent | string | Your colour, like "#1f6feb". Text on it is white or black, whichever reads better. |
website | string | Optional. An https:// address, linked from the checkout's footer. |
logo_png_base64 | string | Optional. A PNG logo in base64: at most 32 KB and 1024 x 512 pixels. It is shown about 28 pixels high on the top bar in your colour, so a logo on a transparent background works best. SVG and other formats are refused. |
{"partner_id": "ptn_K4M2Q7XZ3A", "brand": {"name": "Example Payments", "accent": "#1f6feb", "website": "https://example.com/",
"logo_url": "/brand/ptn_K4M2Q7XZ3A/5e1a…c90d.png", "updated_at": "2026-11-15T10:05:00.000Z"}}
A new PUT replaces the whole brand, and a new logo gets a new URL. GET /v1/partner/brand reads it and DELETE /v1/partner/brand removes it, so your stores' checkouts show Caravel Pay's brand again. The body may be up to 48 KiB.
Your records#
| Request | Answer |
|---|---|
GET /v1/partner | Your partner id, wallet, status and highest fee. |
GET /v1/partner/merchants?limit=&cursor= | The stores you connected, with the fee each one pays you. |
GET /v1/partner/brand | Your checkout brand, or null. |
All three are signed. Every fee you were paid is an ordinary transfer to your wallet on Polygon, so your wallet's history is the full record.
How your fee is paid#
- On the same base as the 2% Caravel fee: a share of the order amount, rounded half up to the cent. Both come off what the store receives.
- In the same settlement: the store's payout, the 2% Caravel fee and your fee leave the checkout's own payment address as three transfers. The checkout is
paidonly when all three are confirmed. A retry never pays twice. - In the token that was paid: card payments and USDC pay out in USDC; a payment in POL pays all three in POL. You need no POL for gas.
- Refunds and disputes work as on every checkout (see refunds). Neither the 2% Caravel fee nor your fee is returned.
Errors#
The usual error format, plus unknown_partner (401: Caravel-Partner is missing or unknown) and partner_paused (403: this partner can't connect stores or change its brand; stores it already connected keep working).