Skip to main content

API Reference

The SDK covers order creation. Everything else you may want from your backend, such as looking up an order or listing settled payments, is a plain HTTP call against the same API. This section documents those endpoints.

Base URL​

https://api.pay.peer.xyz

Every path on these pages is relative to that host and lives under /api/v1.

Authentication​

Merchant endpoints take your API key in the X-API-Key header. The key lives under Settings, Developer in the merchant dashboard; see API keys and webhooks for rotation rules.

curl https://api.pay.peer.xyz/api/v1/merchants/me/orders \
-H "X-API-Key: $ZKPAY_API_KEY"
Server-side only

An API key reads and creates orders for your whole account. Send it only from a trusted backend, never from browser or mobile code.

Sandbox and live are separate accounts with separate keys. A sandbox key sees only sandbox orders and payments, and a live key sees only live ones. A missing or unknown key returns 401 with the message Missing API key or Invalid API key.

One endpoint, get an order by id, takes no key at all. It is the read the hosted checkout page uses, and the order id is the only credential.

IP allowlist​

Owners can restrict an API key to specific source IPs under Settings, Developer, API key IP allowlist. Entries are public IPv4/IPv6 addresses or CIDR blocks (IPv4 prefix 8 to 32, IPv6 prefix 32 to 128, up to 50 entries). A bare address is stored as /32 or /128. Live and sandbox keys have separate lists. An empty list turns the allowlist off.

When a request arrives from an address outside the list, the API responds 403:

{
"success": false,
"message": "IP address 203.0.114.7 is not in this merchant's API key IP allowlist",
"responseObject": { "ip": "203.0.114.7", "reason": "IP_NOT_ALLOWED" },
"statusCode": 403,
"errorCode": "IP_NOT_ALLOWED"
}

reason is IP_NOT_ALLOWED when the caller's address was resolved but is not listed, and CLIENT_IP_UNRESOLVED (message Client IP could not be resolved for API key IP allowlist enforcement) when the connecting address could not be established, for example a request that did not arrive through the public ingress. The ip field is the address to add if the caller is yours. Dashboard sessions never use the API key and are unaffected. Peer-operated integrations (currently the Telegram bot) are exempt from merchant allowlists.

Response envelope​

Every response, success or failure, is a JSON object with the same four fields:

{
"success": true,
"message": "Merchant orders retrieved",
"responseObject": { "...": "..." },
"statusCode": 200
}
FieldTypeDescription
successbooleantrue for a 2xx response
messagestringWritten for a log line, not for matching
responseObjectobject | nullThe payload. null on most failures
statusCodenumberMirrors the HTTP status

In TypeScript, one type covers every response on these pages. The examples reuse it:

type Envelope<T> = { success: boolean; message: string; responseObject: T | null; statusCode: number };

A 400 from query or body validation carries the validation detail in responseObject:

{
"success": false,
"message": "Invalid request",
"responseObject": {
"formErrors": [],
"fieldErrors": { "limit": ["Number must be less than or equal to 100"] }
},
"statusCode": 400
}

Some failures also carry a top-level errorCode string you can branch on. The order creation codes, such as AMOUNT_ABOVE_MAX for an order above the 10,000 USDC order maximum, are listed under createCheckout errors. Starting a payment can also return INTENT_ABOVE_MAX when a bank or app payment would settle more than 10,000 USDC once buyer-paid fees are added.

Common errors​

Three failures behave the same on every endpoint on these pages, so the endpoint pages list only what is specific to them:

StatusCause
400A path parameter, query parameter, or body failed validation. Detail in responseObject.fieldErrors
401Missing or invalid API key. Never returned by the keyless order read
429Rate limit exceeded

Pagination​

The order and payment lists page with two query parameters and report the total (list payments for an order returns every row for one order and takes neither):

ParameterDefaultMaxDescription
page1none1-based page number
limit20100Rows per page

The payload carries the rows, total matching rows across all pages, and the page and limit that were applied. Rows are always newest first. A limit above 100 is rejected with 400, not clamped.

Dates and amounts​

Timestamps are ISO 8601 strings in UTC, for example 2026-09-03T10:15:42.318Z. Amounts are decimal strings, never floats: "25" or "12.5" for USDC fields and "23.14" for fiat fields. Trailing zeros are not preserved, so parse rather than string-compare. Chain ids are decimal strings too, so Base is "8453".

Rate limits​

The API allows 1200 requests per minute per source IP across all endpoints. Past that it answers 429 with the message Too many requests and three headers:

HeaderValue
x-retry-afterSeconds until you may retry
x-rate-limit-remainingRequests left in the current window
x-rate-limiter-resets-atISO 8601 time the window resets

Remediation endpoints add a per-merchant limit (30 actions and 120 reads per minute) with the same 429 shape.

There is no standard Retry-After header. Webhook deliveries are what tell you an order changed; poll these endpoints for reconciliation, not as your primary signal.

Endpoints​

EndpointAuthDocumented at
POST /api/v1/ordersAPI keycreateCheckout; open-amount orders
GET /api/v1/merchants/meAPI keygetMerchant
GET /api/v1/merchants/me/tier-usageAPI keyWatching your usage
POST /api/v1/merchants/me/quotes/availabilityAPI keycheckQuoteAvailability
GET /api/v1/merchants/me/ordersAPI keyList orders
GET /api/v1/orders/{orderId}NoneGet an order by id
GET /api/v1/merchants/me/paymentsAPI keyList payments
GET /api/v1/merchants/me/orders/{orderId}/paymentsAPI keyList payments for an order
POST /api/v1/merchants/me/orders/{orderId}/payments/{paymentId}/recreateAPI keyRecreate
POST /api/v1/merchants/me/orders/{orderId}/payments/{paymentId}/extendAPI keyExtend
POST /api/v1/merchants/me/orders/{orderId}/payments/{paymentId}/fulfill-sarAPI keyFulfill from a transaction id
GET /api/v1/merchants/me/orders/{orderId}/actions/{actionId}API keyPoll an action
POST /api/v1/webhooksAPI keyCreate a webhook
POST /api/v1/sandbox/test-orderSandbox keyLet a coding agent integrate
GET /api/v1/integration/statusAPI keyLet a coding agent integrate

Cancelling an order and reading a single order through the merchant-scoped path need a dashboard session, so they are not available to an API key. Cancel from the dashboard.