https://your-portal-host/api/partner/v1JSON requests and responses use camelCase. Money amounts are integer cents in USD. Each key belongs to one partner account.
Quickstart
- Create a test key in the partner portal under Developer API. Save the full key when shown. Don't see Developer API? Ask your administrator to turn on API access for your account.
- Confirm your key with GET /me:
curl "https://your-portal-host/api/partner/v1/me" \
-H "Authorization: Bearer ptk_test_YOUR_KEY"Submit a deal with a unique idempotency key, then simulate a status change using the returned deal id:
curl -X POST "https://your-portal-host/api/partner/v1/deals" \
-H "Authorization: Bearer ptk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: your-deal-id-123" \
-d '{"contactName":"Jordan Lee","companyName":"Lee Coffee Bar"}'
curl -X POST "https://your-portal-host/api/partner/v1/sandbox/deals/DEAL_ID/simulate" \
-H "Authorization: Bearer ptk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"approved"}'Authentication
Send Authorization: Bearer ptk_live_... for live requests or Authorization: Bearer ptk_test_... for test requests. Keep keys server-side and never commit or expose them in a browser.
Every response includes X-Request-Id for troubleshooting. During key rotation grace, X-Api-Key-Grace-Ends gives the grace deadline.
Authorization: Bearer ptk_live_... (live) or ptk_test_... (test). Create keys in the partner portal under Developer API. The full key is shown once.
Test mode
Test keys access sandbox data only: they never email sales or create real deals. Use /sandbox/deals/{id}/simulate to change a test deal status and /sandbox/residuals/simulate to trigger a test residuals webhook. Live keys cannot use sandbox endpoints.
Idempotency
Send an Idempotency-Key header on deal creation. A retry with the same key and body returns the original response with Idempotent-Replayed: true; reusing a key with a different body returns 409 idempotency_conflict.
Pagination
Pass limit and optional cursor when listing deals. Read hasMore and pass the returned nextCursor as the next request's cursor until no more results remain.
Rate limits
Each key is limited to 120 requests per minute. A 429 rate_limited response includes Retry-After (seconds to wait) and X-RateLimit-Limit headers.
Errors
Failures use the shape {"error":{"code":"...","message":"...","details":{}}}; details is optional. Handle these codes:
unauthorizedforbiddennot_foundvalidation_erroridempotency_conflictrate_limitedtest_mode_onlyinsufficient_permissiondeal_lockedinternal_errorDeal statuses
Stable public deal status. received: submitted, not yet worked. in_progress: being worked by the sales team. application_submitted: merchant application submitted for underwriting. approved: application approved. boarding: merchant account being set up. live: processing. declined: application declined. closed: closed without an account (e.g. merchant not interested).
receivedin_progressapplication_submittedapprovedboardinglivedeclinedclosedWebhooks
Configure your endpoint and signing secret (whsec_...) in the partner portal. Events are delivered at least once; deduplicate by X-Webhook-Id. X-Webhook-Event identifies the event type.
Verify X-Webhook-Signature in the form t=<unix>,v1=<hex>. The v1 value is the hex HMAC-SHA256 of `${t}.${rawBody}` using your endpoint signing secret. Use the exact raw request body, reject timestamps where |now − t| exceeds 300 seconds, and compare signatures in constant time.
Return any 2xx within 10 seconds. Failed deliveries are retried with exponential backoff, up to 8 attempts.
Events
deal.createdA deal credited to you was created.
{
"id": "evt_example",
"object": "event",
"type": "deal.created",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"deal": {
"id": 123,
"contactName": "Jordan Lee",
"companyName": "Lee Coffee Bar",
"status": "received",
"createdAt": "2026-09-29T12:00:00Z"
}
}
}deal.status_changedA deal's public status changed. Moves inside one public status never fire.
{
"id": "evt_example",
"object": "event",
"type": "deal.status_changed",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"deal": {
"id": 123,
"status": "in_progress",
"previousStatus": "received",
"changedAt": "2026-09-29T12:00:00Z"
}
}
}deal.approvedA deal moved to approved (also sends deal.status_changed).
{
"id": "evt_example",
"object": "event",
"type": "deal.approved",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"deal": {
"id": 123,
"status": "approved",
"previousStatus": "in_progress",
"changedAt": "2026-09-29T12:00:00Z"
}
}
}deal.declinedA deal was declined (also sends deal.status_changed).
{
"id": "evt_example",
"object": "event",
"type": "deal.declined",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"deal": {
"id": 123,
"status": "declined",
"previousStatus": "in_progress",
"changedAt": "2026-09-29T12:00:00Z"
}
}
}residuals.postedResiduals for a month were posted or updated for you.
{
"id": "evt_example",
"object": "event",
"type": "residuals.posted",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"residuals": {
"month": "2026-09"
}
}
}webhook.testA test delivery to verify your endpoint.
{
"id": "evt_example",
"object": "event",
"type": "webhook.test",
"createdAt": "2026-09-29T12:00:00Z",
"livemode": false,
"data": {
"test": true,
"message": "This is a test event from the partner portal.",
"endpointId": 42
}
}Verify in Node.js
const { createHmac, timingSafeEqual } = require("node:crypto");
function verifyWebhook(rawBody, signature, secret) {
const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signature || "");
if (!match) return false;
const [, t, hex] = match;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(t + ".").update(rawBody).digest();
const received = Buffer.from(hex, "hex");
return received.length === expected.length &&
timingSafeEqual(received, expected);
}
// Pass the unparsed request body (Buffer) and your whsec_... secret.Verify in Python
import hmac
import hashlib
import time
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
try:
parts = dict(part.split("=", 1) for part in signature.split(","))
timestamp, received = parts["t"], parts["v1"]
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode(), timestamp.encode() + b"." + raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)
except (ValueError, KeyError):
return FalsePermissions & key lifecycle
read_only keys can only use GET endpoints. read_write keys can also create or change resources. Writing with a read-only key returns 403 insufficient_permission.
Keys can have an optional expiry. An expired key returns 401 unauthorized. Rotating a key issues a new secret; the previous key remains usable until its rotation grace deadline (up to seven days). After grace, the old key is refused. Revoke a key to disable it immediately.
Integration endpoints
Recover missed webhooks with GET /events (filter by type, createdAfter and cursor) and GET /events/{id}. Events are returned newest first.
Update a submitted deal with PATCH /deals/{id} while it is received or in progress; withdraw with POST /deals/{id}/withdraw and an optional reason. Once locked, updates return 409 deal_locked.
Use GET /merchants to list your live merchants with anonymized processor names. Each merchant carries dealId, the deal it was boarded from (null when it is not linked to one of your deals).
API reference
Endpoints below are relative to the base URL. Examples and response definitions come from the published OpenAPI specification.
/openapi.jsonSpecDownload this API specification
Responses
200 This document.
application/json
{
"type": "object"
}/meAccountIdentify the partner and key
Handy first call to confirm your key works and whether it is live or test.
Responses
200 The partner behind this key.
application/json
{
"object": "partner",
"livemode": false,
"partner": {
"id": 731,
"name": "Acme Referral Partners"
},
"apiKey": {
"id": 12,
"mode": "test"
}
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/dealsDealsSubmit a deal
Creates a merchant lead credited to your partner account, exactly like a portal submission. Only contactName is required; unknown fields are ignored. Send an Idempotency-Key header so a retried request never creates a duplicate: repeating the same key with the same body returns the original response (with Idempotent-Replayed: true); the same key with a different body returns 409 idempotency_conflict. Test keys create sandbox deals only.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| Idempotency-Key | header | No | Unique value per logical deal, e.g. your own record id. |
Request body · required
application/json
{
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550142",
"monthlyVolume": "45000",
"averageTicket": "12.50",
"businessCity": "Miami",
"businessState": "FL",
"notes": "Wants countertop terminal"
}Responses
201 Deal created (or replayed).
application/json
{
"id": 48213,
"object": "deal",
"livemode": true,
"status": "received",
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550142",
"monthlyVolume": "45000",
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-03T09:12:40.000Z"
}400 Validation failed (validation_error).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}409 Idempotency-Key reused with a different body (idempotency_conflict).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/dealsDealsList your deals
Newest first. Page with cursor = the previous page's nextCursor.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| limit | query | No | {"type":"integer","minimum":1,"maximum":100,"default":25} |
| cursor | query | No | Return deals older than this id. |
| status | query | No | {"$ref":"#/components/schemas/DealStatus"} |
Responses
200 A page of deals.
application/json
{
"object": "list",
"livemode": true,
"data": [
{
"id": 48213,
"object": "deal",
"livemode": true,
"status": "in_progress",
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550142",
"monthlyVolume": "45000",
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-03T09:12:40.000Z"
}
],
"hasMore": false,
"nextCursor": null
}400 Invalid query parameter.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/deals/{id}DealsGet one deal with its status history
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| id | path | Yes | Deal id. |
Responses
200 The deal.
application/json
{
"id": 48213,
"object": "deal",
"livemode": true,
"status": "in_progress",
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550142",
"monthlyVolume": "45000",
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-03T09:12:40.000Z",
"history": [
{
"status": "received",
"at": "2026-09-01T14:03:11.000Z"
},
{
"status": "in_progress",
"at": "2026-09-02T16:20:05.000Z"
}
]
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}404 No such deal for this key (not_found). Deals belonging to anyone else also return 404.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/deals/{id}DealsUpdate a deal
Updates fields on a deal you submitted while its status is received or in_progress. Once the deal reaches application_submitted (underwriting) or later, returns 409 deal_locked. Requires a read-write key. Safe to retry.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| id | path | Yes | Deal id. |
Request body · required
application/json
{
"contactPhone": "+13055550199",
"monthlyVolume": "52000"
}Responses
200 The updated deal.
application/json
{
"id": 48213,
"object": "deal",
"livemode": true,
"status": "in_progress",
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550199",
"monthlyVolume": "52000",
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-03T09:12:40.000Z"
}400 Invalid input (validation_error).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}404 No such resource for this key (not_found). Resources belonging to anyone else also return 404.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}409 The deal is in underwriting or later and can no longer be changed through the API (deal_locked). details.status holds its current status.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/deals/{id}/withdrawDealsWithdraw a deal
Withdraws a deal you submitted while it is received or in_progress; the deal moves to closed and a deal.status_changed event fires. Idempotent: withdrawing an already-withdrawn deal returns it unchanged. Returns 409 deal_locked once the deal is in underwriting or later. Requires a read-write key.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| id | path | Yes | Deal id. |
Request body · optional
application/json
{
"reason": "Merchant signed elsewhere"
}Responses
200 The withdrawn deal.
application/json
{
"id": 48213,
"object": "deal",
"livemode": true,
"status": "closed",
"contactName": "Jordan Lee",
"companyName": "Lee's Coffee Bar",
"contactEmail": "jordan@leescoffee.example",
"contactPhone": "+13055550142",
"monthlyVolume": "45000",
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-03T09:12:40.000Z"
}400 Invalid input (validation_error).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}404 No such resource for this key (not_found). Resources belonging to anyone else also return 404.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}409 The deal is in underwriting or later and can no longer be changed through the API (deal_locked). details.status holds its current status.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/merchantsMerchantsList live merchants
Your merchants that are live, newest first. The processor is an anonymized lane label; real processor names are never returned. Test keys list sandbox deals simulated to live.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| limit | query | No | {"type":"integer","minimum":1,"maximum":100,"default":25} |
| cursor | query | No | nextCursor from the previous page. |
Responses
200 A page of merchants.
application/json
{
"object": "list",
"livemode": true,
"data": [
{
"object": "merchant",
"id": 912,
"livemode": true,
"businessName": "Lee's Coffee Bar",
"dba": "Lee's Coffee",
"mccCode": "5814",
"status": "live",
"processor": "Lane 2",
"liveSince": "2026-09-20"
}
],
"hasMore": false,
"nextCursor": null
}400 Invalid input (validation_error).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/eventsEventsList events
Events for this key's partner and mode, newest first. Use it to recover events your webhook endpoint missed. Events are kept for 90 days and are recorded while you hold an API key of that mode.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| type | query | No | {"$ref":"#/components/schemas/EventType"} |
| createdAfter | query | No | Only events created after this time. |
| limit | query | No | {"type":"integer","minimum":1,"maximum":100,"default":25} |
| cursor | query | No | nextCursor from the previous page. |
Responses
200 A page of events.
application/json
{
"object": "list",
"livemode": true,
"data": [
{
"object": "event",
"id": "evt_3f9a0c1e8b7d4a2f9e6c5b4a3d2e1f00",
"type": "deal.status_changed",
"createdAt": "2026-09-02T16:20:05.000Z",
"livemode": true,
"data": {
"deal": {
"id": 48213,
"status": "in_progress",
"previousStatus": "received",
"changedAt": "2026-09-02T16:20:05.000Z"
}
}
}
],
"hasMore": false,
"nextCursor": null
}400 Invalid input (validation_error).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/events/{id}EventsGet one event
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| id | path | Yes | Event id (evt_...), the same id sent in the webhook body. |
Responses
200 The event.
application/json
{
"object": "event",
"id": "evt_3f9a0c1e8b7d4a2f9e6c5b4a3d2e1f00",
"type": "deal.status_changed",
"createdAt": "2026-09-02T16:20:05.000Z",
"livemode": true,
"data": {
"deal": {
"id": 48213,
"status": "in_progress",
"previousStatus": "received",
"changedAt": "2026-09-02T16:20:05.000Z"
}
}
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}404 No such resource for this key (not_found). Resources belonging to anyone else also return 404.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/residualsResidualsResiduals by month
Monthly totals and per-merchant lines for your account — the same figures as the Earnings view in your partner portal. Defaults to the last 12 months; at most 24 months per request.
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| from | query | No | Example: 2026-01 |
| to | query | No | Example: 2026-08 |
Responses
200 Residual report.
application/json
{
"object": "residual_report",
"livemode": true,
"currency": "USD",
"from": "2026-07",
"to": "2026-08",
"totals": {
"volumeCents": 9120000,
"earningsCents": 22800,
"lineCount": 2
},
"months": [
{
"month": "2026-08",
"volumeCents": 4600000,
"earningsCents": 11500,
"lines": [
{
"merchantId": 9001,
"merchantName": "Lee's Coffee Bar",
"type": "merchant",
"volumeCents": 4600000,
"earningsCents": 11500
}
]
},
{
"month": "2026-07",
"volumeCents": 4520000,
"earningsCents": 11300,
"lines": [
{
"merchantId": 9001,
"merchantName": "Lee's Coffee Bar",
"type": "merchant",
"volumeCents": 4520000,
"earningsCents": 11300
}
]
}
]
}400 Invalid month range.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/residuals/{month}ResidualsResiduals for one month
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| month | path | Yes | Example: 2026-08 |
Responses
200 One month.
application/json
{
"object": "residual_month",
"livemode": true,
"currency": "USD",
"month": "2026-08",
"volumeCents": 4600000,
"earningsCents": 11500,
"lines": [
{
"merchantId": 9001,
"merchantName": "Lee's Coffee Bar",
"type": "merchant",
"volumeCents": 4600000,
"earningsCents": 11500
}
]
}400 Invalid month.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 The partner account behind the key is not active (forbidden), or a read-only key called a write endpoint (insufficient_permission).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/sandbox/deals/{id}/simulateSandboxMove a sandbox deal to a status
Test keys only. Changes a sandbox deal's status and fires the same webhooks a real change would (deal.status_changed, plus deal.approved/deal.declined).
Parameters
| Name | In | Required | Details |
|---|---|---|---|
| id | path | Yes | Deal id. |
Request body · required
application/json
{
"status": "approved"
}Responses
200 The updated sandbox deal.
application/json
{
"$ref": "#/components/schemas/DealWithHistory"
}400 Invalid status.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 Live key used (test_mode_only).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}404 No such sandbox deal.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}/sandbox/residuals/simulateSandboxFire a residuals.posted webhook
Test keys only. Sends a test-mode residuals.posted event to your test webhook endpoints.
Request body · optional
application/json
{
"month": "2026-08"
}Responses
202 Event queued.
application/json
{
"$ref": "#/components/schemas/SimulatedEvent"
}400 Invalid month.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}401 Missing, malformed, revoked, expired or unknown key, or a rotated key past its grace period (unauthorized).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}403 Live key used (test_mode_only).
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}429 Too many requests for this key (rate_limited). Wait for Retry-After seconds.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}500 Unexpected server error (internal_error). Safe to retry; use an Idempotency-Key for writes.
application/json · error response
{
"$ref": "#/components/schemas/ErrorResponse"
}Changelog
2026-09-29 · Developer API expansion
Events retrieval, merchant listing, deal updates and withdrawals, key permissions and expiry, rotation grace, request IDs and expanded webhook management.
Initial v1 release
Partner API launched with deal submission, residuals, sandbox testing and signed webhooks.