Quote Availability
checkQuoteAvailability tells your backend whether an amount can be paid right now, before you create an order. When it cannot, the response carries nearby amounts that can.
This call sends your merchant API key in the X-API-Key header. Call it only from a trusted backend.
Executable amounts and fee buffers
Checkout can use a deposit with a different executable amount without changing the order. The pricing mode determines whether that deposit may be smaller or larger, who covers the difference, and how the maximum fee applies. Eligible quotes compete on the buyer's price within the selected release-flow pool.
| Pricing mode | Maximum fee | Amount buffer | Who covers the difference |
|---|---|---|---|
Standard merchant pays, checkout / exact-fiat | Configured fees + spread + bridge spread | Smaller release within the remaining cap | Merchant receives less; Peer keeps its scheduled fee |
| Standard buyer pays | Same additive check for ordinary quotes; actual buyer total for larger deposits | Larger release within the cap; no smaller-release subsidy | Merchant keeps the full order amount; Peer collects scheduled fees plus surplus |
| Standard split, buyer share between 0% and 100% | Configured fees + spread + bridge spread | Smaller release within the derived band, limited to Peer's fee budget | Peer reduces its fee; expected merchant net and partner fee rates stay protected |
| Flat all-in, any fee payer | Configured all-in fee budget; maxFeeConfig is not used | Up to 20 basis points (0.2%) smaller | Peer funds the shortfall if its remaining fee covers it |
Explicitly cleared cap, or merchant-paid exact-token checks | No max-fee check when cleared | Up to 20 basis points (0.2%) smaller; no larger-deposit search | Peer funds the shortfall if its fee covers it |
A split of 0% buyer share uses merchant-pays rules; 100% uses buyer-pays rules. New merchant configurations keep the 20% maximum fee default. This is separate from the 0.2% legacy amount buffer. Allowances round down to whole USDC base units; larger-deposit caps round down to payable fiat cents.
For example, an 85 USDC buyer-paid order, with a 9.25% scheduled fee and a 20% cap, can use a fixed 100 USDC deposit at 1:1. The buyer pays $100 to the liquidity provider, the merchant receives 85 USDC, and Peer collects 15 USDC: 9.25 scheduled plus 5.75 surplus. Partner fee rates remain unchanged when configured, and uncollectable fee rounding dust stays with the merchant. A cheaper exact-size quote still wins. A fixed 100 USDC deposit cannot fund a 95 USDC order at that scheduled fee: only 90.75 USDC remains after fees.
For merchant-paid fees, a 10% cap with a 6.5% scheduled fee permits about a 3.38% shortfall before bridge costs. The shortfall counts as spread on the smaller amount, reducing merchant proceeds. With a Peer-funded 0.2% buffer, a $50 request can use a $49.96 quote only if Peer's fee covers the difference while preserving the expected merchant net.
Ordinary quotes retain the additive cap calculation; it is not a literal cap on the buyer's markup over the order. With a 9.25% fee and a 10% spread, an ordinary 85 USDC buyer-paid quote passes a 20% cap (19.25% additive) even though the buyer pays $103.04. A larger deposit for that order must instead cost no more than $102.00. See how the cap is checked.
SAR, whitelist, and fee-cap rules still apply. An executable buffered quote counts
toward quoteCount and is not a nearby suggestion requiring a different order
amount.
Basic usage
import { checkQuoteAvailability, CheckoutMode } from '@zkp2p/pay-sdk';
const availability = await checkQuoteAvailability(
{
amount: '25.00',
quoteMode: CheckoutMode.EXACT_TOKEN,
enabledRails: ['venmo'],
destinationChainId: 8453,
destinationToken: 'USDC',
destinationAddress: '0xYourPayoutWallet',
},
{
apiBaseUrl: 'https://api.pay.peer.xyz',
apiKey: process.env.ZKPAY_API_KEY!,
signal: AbortSignal.timeout(8_000),
},
);
if (availability.available) {
// Create the order with the same enabledRails: ['venmo'] selection.
}
Pass a signal so a slow response cannot block your own request handler.
Rail-scoped availability
Availability means at least one of the checked rails can fill the amount. Only fiat rails are checked.
By default the API checks every rail enabled on your account, which does not guarantee liquidity for any one payment method. A Venmo-only checkout can pass an account-wide check on Cash App or PayPal liquidity while Venmo has no quote inside your fee cap. To scope the check, send the same non-empty enabledRails array, amount, quote mode, currency, and payout destination you will send to createCheckout. An explicit enabledRails replaces your account defaults here exactly as it does at order creation.
Parameters
params: QuoteAvailabilityRequest
| Property | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Positive decimal string. Interpreted by quoteMode |
quoteMode | CheckoutModeType | Yes | 'exact-token' or 'exact-fiat', see below |
enabledRails | string[] | No | Non-empty list of supported rails. Defaults to your account's rails. See rail-scoped availability |
destinationChainId | string | number | Yes | Payout chain, decimal only: 8453 or '8453' for Base. Hex ('0x2105') and signed strings ('+8453') are rejected with a TypeError before any request is made |
destinationToken | string | Yes | Payout token, for example 'USDC' |
destinationAddress | string | Yes | Payout address |
fiatCurrency | string | No | Falls back to your configured default payment currency, then to USD |
nearbyQuotesCount | number | No | Suggestions per direction, 1 to 10, default 3 |
CheckoutModeType is the type; CheckoutMode is the const object you read values from (CheckoutMode.EXACT_TOKEN). Annotate with CheckoutModeType.
exact-token vs exact-fiat
CheckoutMode.EXACT_TOKEN:amountis the order principal in USDC. In buyer pays mode this is the net USDC target.CheckoutMode.EXACT_FIAT:amountis the order principal denominated infiatCurrency.
Availability uses the merchant's current fee payer and buyer share. In SPLIT mode,
the API includes the buyer share of quote costs when probing liquidity. The buyer
charge depends on the fee share and the quote. The merchant receives the full
principal only at a 100% buyer share; otherwise the merchant share reduces the
net settlement. This endpoint does not accept per-order
fee overrides; its result applies to orders using the same merchant fee settings.
Use the same mode you intend to use at order creation. Suggested amounts come back in the units of the mode you asked for.
Response
type QuoteAvailability = {
available: boolean;
quoteCount: number;
nearbySuggestions: {
below: QuoteAvailabilitySuggestion[];
above: QuoteAvailabilitySuggestion[];
} | null;
};
| Property | Type | Description |
|---|---|---|
available | boolean | Whether at least one quote on the checked rails can currently fill amount under your fee settings |
quoteCount | number | How many quotes matched |
nearbySuggestions | object | null | Alternative amounts. Always null when available is true |
Each suggestion:
| Property | Type | Description |
|---|---|---|
suggestedAmount | string | The alternative order principal, in your requested quoteMode units. For SPLIT it excludes the buyer share of fees |
percentDifference | string | Signed difference from your requested amount |
rail | string | Payment rail that can fill it, for example 'venmo' |
paymentAmount | string | What the customer pays |
paymentCurrency | string | Currency of paymentAmount |
tokenAmount | string | For SPLIT, order principal in USDC after allocating quote costs between buyer and merchant by the saved fee share. Otherwise, USDC from the quote |
conversionRate | string | Rate used for the quote |
below holds amounts smaller than what you asked for, above larger. Under
SPLIT, create or resize the order using suggestedAmount; do not use the gross
paymentAmount as the principal or the buyer share of fees would be added twice. An
exact-token request always returns USDC principal suggestions, even when the
underlying liquidity quote is in another fiat currency. tokenAmount in split
suggestions is principal, not the eventual payout: rail fees and spread still
affect what settles to the merchant.
Behavior notes
- Suggestions only appear when the amount is unavailable.
nearbySuggestionsis alwaysnullwhenavailableistrue. available: falsedoes not guarantee suggestions. You can getnearbySuggestions: nullor{ below: [], above: [] }. Both mean there are no alternatives to offer, so check fornulland for empty arrays.nearbyQuotesCountis per direction. It capsbelowandaboveindependently, sonearbyQuotesCount: 10can return up to 20 suggestions.- The order and per-payment limits apply. An
amountabove 10,000 USDC fails with400 AMOUNT_ABOVE_MAX, as it would at order creation. Inexact-fiatmode the converted USDC amount is checked. A quote whose payment would settle more than 10,000 USDC on chain, fees included, does not count, so a buyer-pays amount near the limit can returnavailable: false. Nearby suggestions are limited so that the order amount, converted to USDC as order creation converts it, and the on-chain payment with buyer fees both stay at or below 10,000 USDC. See order amount limits. available: trueis advisory. The check reserves nothing. Liquidity can move between the check and order creation, so always handle order-creation failure.
Where the destination fields come from
All three destination fields are required here, even though createCheckout treats them as optional and falls back to your account configuration.
getMerchant supplies two of them:
import { getMerchant } from '@zkp2p/pay-sdk';
const merchant = await getMerchant({ apiBaseUrl, apiKey });
if (merchant.merchantConfig === null) {
throw new Error('Merchant config is not set up yet.');
}
const { destinationChainId, destinationToken } = merchant.merchantConfig;
There is no destinationAddress on merchantConfig. Pass the same destinationAddress you pass to createCheckout. If you omit it there and rely on configuration defaults, use the payout address from your dashboard payout settings.
Re-read this per checkout flow rather than caching it indefinitely, because payout configuration can change.
Worked example
Check first, then either create the order or offer the customer an amount that works:
import { checkQuoteAvailability, createCheckout, CheckoutMode } from '@zkp2p/pay-sdk';
const params = {
amount: '25.00',
quoteMode: CheckoutMode.EXACT_TOKEN,
enabledRails: ['venmo'],
destinationChainId: 8453,
destinationToken: 'USDC',
destinationAddress: '0xYourPayoutWallet',
};
// A fresh signal per call. AbortSignal.timeout starts counting when it is created,
// so a shared signal gives the second call only whatever time the first left over.
const opts = () => ({
apiBaseUrl: 'https://api.pay.peer.xyz',
checkoutBaseUrl: 'https://pay.peer.xyz',
apiKey: process.env.ZKPAY_API_KEY!,
signal: AbortSignal.timeout(8_000),
});
const availability = await checkQuoteAvailability(params, opts());
if (availability.available) {
const checkout = await createCheckout(
{ ...orderFields, requestedUsdcAmount: params.amount, enabledRails: params.enabledRails },
opts(),
);
return { checkoutUrl: checkout.checkoutUrl };
}
const suggestions = availability.nearbySuggestions;
const alternatives = [
...(suggestions?.below ?? []),
...(suggestions?.above ?? []),
];
if (alternatives.length === 0) {
return { status: 'no_liquidity' };
}
return {
status: 'alternatives',
options: alternatives.map((option) => ({
amount: option.suggestedAmount,
rail: option.rail,
customerPays: `${option.paymentAmount} ${option.paymentCurrency}`,
})),
};
Common errors
The SDK throws a PayApiError carrying the HTTP status and server detail. This table covers what you are most likely to hit; it is not exhaustive.
| Status | Message | Cause |
|---|---|---|
400 | Invalid request: nearbyQuotesCount: Number must be less than or equal to 10 | A field failed validation. The message summarizes the field errors; complete detail is on fieldErrors and formErrors |
400 | Merchant config not found | The account has no checkout configuration yet |
400 | Requested USDC amount must be at most 10000 USDC | errorCode AMOUNT_ABOVE_MAX: the amount, converted to USDC in exact-fiat mode, is above the 10,000 USDC order limit |
401 | Missing or invalid API key | |
429 | Too many requests | Rate limited. See the x-retry-after header below |
502 | Unable to resolve exchange rate for <CUR> | Exchange-rate lookup failed for that currency |
502 | Quote provider unavailable | Quotes could not be fetched. Retry |
502 | Unable to price non-Base payout for quote availability | The non-Base payout could not be priced |
500 | Unexpected server error |
Retries
PayApiError carries the HTTP status as statusCode, but not the response headers. To inspect headers such as rate-limit metadata, pass a custom fetcher and inspect the response before returning it:
const fetcher: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (response.status === 429) {
// Seconds until the limit resets. Rate-limited responses use x-retry-after,
// not a standard Retry-After header.
recordRetryAfter(Number(response.headers.get('x-retry-after')));
}
return response;
};
Rate-limited responses carry three headers:
| Header | Value |
|---|---|
x-retry-after | Seconds until you may retry |
x-rate-limit-remaining | Requests left in the current window |
x-rate-limiter-resets-at | ISO timestamp when the window resets |
Malformed responses
The SDK never hands back a partially valid object. If a response does not match the documented shape it throws, and the message tells you which layer rejected it:
| Message | Meaning |
|---|---|
Malformed API response envelope: … | The outer { success, responseObject } envelope was wrong or absent, usually a proxy or gateway returning something that is not the API's response |
Malformed quote availability response | The envelope was fine, but the availability payload was not, for example a bad quoteCount or a suggestion missing a field |
Both are plain Errors, not PayApiError.