Skip to main content

Orders

An order is the thing you create with createCheckout. This page covers reading it back. There are two reads:

EndpointAuthUse it when
GET /api/v1/merchants/me/ordersAPI keyYou are on your backend and want one order by id, or a filtered page of orders
GET /api/v1/orders/{orderId}NoneYou hold an order id and want the hosted checkout view, including the customer's current payment attempt

Both return the CheckoutOrder shape. Webhooks remain the signal that an order changed; use these reads to reconcile or to answer a support question.

Amount mode​

Every order carries amountMode. Read it before the amounts:

amountModeCreated withAmounts
FIXEDrequestedUsdcAmount, or requestedFiatAmount with requestedFiatCurrencyrequestedUsdcAmount and remainingUsdcAmount are always set
OPENopenAmountrequestedUsdcAmount and remainingUsdcAmount are null until the customer starts a payment. The order also carries openAmount (your currency, minAmount, maxAmount and presets) and amountVersion, which counts how many times the amount was saved

See open-amount orders.

Order status​

status is CREATED, PARTIALLY_FULFILLED, FULFILLED, or CANCELLED. It tracks how much of the order's value has settled, not where the customer is in the payment flow. What each value means, what moves it, and which webhook fires are in order status.

An order never expires on its own. The customer payment window expires the payment attempt and leaves the order CREATED, so a late settlement can still fulfil it. Where the customer is right now lives on the payment, which the hosted read returns as currentPayment and the Payments API lists, across orders or for one order.

Chargebacks and refunds are additive: they sit in chargebackStatus, chargebacks, and the refund* fields and never move status. See chargeback status. Attestation penalties are per-payment facts carried in currentPayment.penalties and on every payment the Payments API returns. See payment penalties.

List orders​

GET /api/v1/merchants/me/orders
X-API-Key: <your key>

Returns your orders, newest first, with optional filters. Pass the full order id in orderId to fetch one order.

Query parameters​

ParameterTypeDescription
orderIdstringCase-insensitive substring match on the order id. A full id matches exactly one order. Ignored when search is set
statusCheckoutOrderStatusCREATED, PARTIALLY_FULFILLED, FULFILLED, or CANCELLED. Ignored when displayStatus is set
displayStatusstringSlices open orders by payment activity. See display status
chargebackStatusOrderChargebackStatusNONE, PARTIALLY_CHARGEBACKED, or CHARGEBACKED
searchstringFree-text search, at least 3 characters. Shorter values are ignored. See search

page and limit follow the shared pagination rules.

Display status​

displayStatus answers "what is the customer doing on this open order". Every value implies status=CREATED, so combining it with status is redundant and status is dropped.

ValueMatches an open order that
ACTIVEHas a payment attempt in progress, so a customer is inside their payment window
EXPIREDHas at least one expired attempt and none in progress. The customer walked away
CREATEDHas no attempt in progress and none expired. Either the customer never picked a payment method, or every attempt was cancelled or failed

search runs a case-insensitive substring match across the order id, the notes JSON you attached at creation, the API's internal metadata JSON, the payment rail (by identifier or by display name, so venmo and Venmo both match), and the payment's id, payTo (recipient handle/address), railIdentifier, railTransactionId (payment-app transaction ID), and fulfillTransaction (settlement transaction hash).

Plain non-negative decimals, optionally prefixed with $, also match the requested fiat amount in order metadata or requestedUsdcAmount by exact numeric equality. For example, $20 and 20.00 match 20.00. Amount searches follow the same three-character minimum, so use $20 or 20.00, not 20. Existing substring matches still apply; % and _ are treated literally.

metadata is written by the API, not by you; notes is the merchant field. Searching for your own order reference works when you put it in notes, as the recommended backend pattern does.

Response​

responseObject is a page:

type MerchantOrderList = {
orders: MerchantOrderListItem[];
total: number;
page: number;
limit: number;
};

type MerchantOrderListItem = CheckoutOrder & {
hasActivePayment: boolean;
hasExpiredPayment: boolean;
paymentSelectedRail: string | null;
netSettledUsdcAmount: string | null;
settledPaymentRails: string[];
settledPaymentCount: number;
};

The six extras are dashboard list signals, derived from payment state rather than stored on the order:

Extra fieldDescription
hasActivePaymentA payment attempt is in progress
hasExpiredPaymentAt least one attempt expired
paymentSelectedRailRail of the attempt in progress, else of the most recent expired one, else null
netSettledUsdcAmountUSDC credited to you across settled payments, after fees. Chargebacks and refunds are not subtracted. null when nothing has settled or any settled payment has no recorded amount.
settledPaymentCountNumber of settled payments, including payments on the same rail. Zero when nothing has settled; refunds require a single settled payment.
settledPaymentRailsDistinct rails of the settled payments, oldest payment first (by when the payment was created). Empty when nothing has settled.

cryptoReferralSplitConfig, the referral split that applies when an order is paid over a crypto rail, is not a list-only extra: it is on the order object every read and every webhook returns. See CheckoutOrder.

List rows also carry bridgeInfo: conversion info from the newest settled payment that has a conversion attempt, using its latest attempt (both by creation time). It is null when no settled payment has a conversion attempt. The hosted read uses the current payment instead. requestedFiat remains hosted-only.

Example​

curl "https://api.pay.peer.xyz/api/v1/merchants/me/orders?orderId=cmf5k2x9d0001abcd1234efgh" \
-H "X-API-Key: $ZKPAY_API_KEY"

Envelope<T> below is the shared response envelope.

async function getOrder(orderId: string) {
const url = new URL('https://api.pay.peer.xyz/api/v1/merchants/me/orders');
url.searchParams.set('orderId', orderId);
url.searchParams.set('limit', '1');

const response = await fetch(url, {
headers: { 'X-API-Key': process.env.ZKPAY_API_KEY! },
signal: AbortSignal.timeout(8_000),
});
const body = (await response.json()) as Envelope<{ orders: MerchantOrderListItem[]; total: number }>;

if (!body.success || body.responseObject === null) {
throw new Error(`${body.statusCode}: ${body.message}`);
}
return body.responseObject.orders[0] ?? null;
}

A page of every open order a customer is actively paying:

curl "https://api.pay.peer.xyz/api/v1/merchants/me/orders?displayStatus=ACTIVE&limit=100" \
-H "X-API-Key: $ZKPAY_API_KEY"

Errors​

The common errors are the only ones this endpoint returns. An orderId that matches nothing is not one of them: you get 200 with an empty orders array and total: 0.

Get an order by id​

GET /api/v1/orders/{orderId}

Returns the same view the hosted checkout page renders: the order, the merchant branding it shows, and the customer's current payment attempt. No API key is needed, and one sent along is ignored; the order id is the credential. Order ids are random and unguessable, so treat them like any other capability URL and do not publish them.

This is the read to use when all you hold is an order id, for example from a checkout.closed event or a success redirect, and you want to know whether the customer actually paid.

Response​

type HostedOrder = {
order: CheckoutOrder;
merchant: CheckoutMerchant;
currentPayment: CheckoutPayment | null;
};

type CheckoutMerchant = {
id: string;
name: string;
logoUrl: string | null;
environment: 'LIVE' | 'SANDBOX';
disableBranding: boolean;
checkoutTheme: unknown | null;
defaultPaymentCurrency: string | null;
verified: boolean;
};
FieldDescription
orderThe CheckoutOrder, including requestedFiat (the fiat price you asked for, present only on an un-resized fiat-input order; on an open-amount order, the saved amount in the currency the customer typed it in), bridgeInfo (cross-chain payout state for the current payment) and applePayChargeUsdcAmount (the USDC the customer would be charged for the remaining balance by Apple Pay, grossed up for Apple Pay fees when the customer pays them; null when Apple Pay is not enabled for the order, the charge cannot be priced, or an open-amount order has no saved amount yet). An open-amount order also carries openAmount with effectiveMin, effectiveMax (the allowed range in openAmount.currency, never above 10,000 USDC worth; both null while the amount is locked, once the order is fulfilled or cancelled, or while the exchange rate is unavailable) and savedInput (in the currency the customer typed), and amountLock
merchantWhat checkout shows about you. environment tells you whether this is a sandbox order
currentPaymentThe newest CheckoutPayment, except that if it was cancelled, expired or failed, the newest older fiat attempt with an open, unexpired on-chain intent is returned; if none is open and the newest attempt was a fiat attempt, the newest settled attempt is returned when one exists. A cancelled, expired or failed crypto attempt otherwise stays current. A funded Zcash deposit still takes priority while converting. Returns null when the customer has never picked a payment method. Read its status and rail for where the customer is, and quote with quoteExpiresAt for what they owe and until when. paymentAmount is "0" until the attempt settles

When the most recent settlement attempt on currentPayment failed onchain, the payment carries errorCode: "FULFILL_INTENT_FAILED" and a human-readable errorMessage.

currentPayment is one attempt selected by this rule. A new active or settled attempt stays current; cancelling, expiring or failing a newer attempt can reveal an older fiat intent that the customer can still pay or finish, or, for a fiat attempt, the newest settled attempt when no older fiat intent is open. Every attempt is visible through the Payments API.

Example​

curl https://api.pay.peer.xyz/api/v1/orders/cmf5k2x9d0001abcd1234efgh
{
"success": true,
"message": "Order retrieved",
"responseObject": {
"order": {
"id": "cmf5k2x9d0001abcd1234efgh",
"status": "CREATED",
"amountMode": "FIXED",
"requestedUsdcAmount": "25",
"remainingUsdcAmount": "25",
"applePayChargeUsdcAmount": "26.31579",
"requestedFiat": null,
"notes": { "merchantOrderId": "order_12345" },
"createdAt": "2026-09-03T10:15:42.318Z",
"...": "..."
},
"merchant": {
"id": "cmf1abc...",
"name": "Example Store",
"environment": "LIVE",
"...": "..."
},
"currentPayment": {
"id": "cmf5k3...",
"status": "CREATED",
"rail": "venmo",
"paymentAmount": "0",
"currency": "USD",
"quoteExpiresAt": "2026-09-03T11:15:42.318Z",
"...": "..."
}
},
"statusCode": 200
}

For an open-amount order that no customer has started paying, currentPayment is null and order reads like this:

{
"id": "cmf5k2x9d0001abcd1234efgh",
"status": "CREATED",
"amountMode": "OPEN",
"requestedUsdcAmount": null,
"remainingUsdcAmount": null,
"amountVersion": 0,
"openAmount": {
"currency": "USD",
"minAmount": "10.00",
"maxAmount": "2000.00",
"presets": ["25.00", "50.00", "100.00"],
"effectiveMin": "10.00",
"effectiveMax": "2000.00",
"savedInput": null
},
"amountLock": null,
"applePayChargeUsdcAmount": null,
"requestedFiat": null,
"...": "..."
}

Errors​

StatusCause
404No order with that id, in any environment

The common errors also apply, except 401: this read takes no API key.

What you cannot do with an API key​

  • Cancel an order. POST /api/v1/merchants/me/orders/{orderId}/cancel needs a dashboard session. Cancel from the dashboard's order list. An open order that is never cancelled stays CREATED indefinitely; it costs nothing and reserves no liquidity until a customer starts paying.
  • Read GET /api/v1/merchants/me/orders/{orderId}. That merchant-scoped single-order route also needs a dashboard session. Use the orderId filter on list orders instead; it returns the same order data.