Skip to main content

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.

Server-side only

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 modeMaximum feeAmount bufferWho covers the difference
Standard merchant pays, checkout / exact-fiatConfigured fees + spread + bridge spreadSmaller release within the remaining capMerchant receives less; Peer keeps its scheduled fee
Standard buyer paysSame additive check for ordinary quotes; actual buyer total for larger depositsLarger release within the cap; no smaller-release subsidyMerchant keeps the full order amount; Peer collects scheduled fees plus surplus
Standard split, buyer share between 0% and 100%Configured fees + spread + bridge spreadSmaller release within the derived band, limited to Peer's fee budgetPeer reduces its fee; expected merchant net and partner fee rates stay protected
Flat all-in, any fee payerConfigured all-in fee budget; maxFeeConfig is not usedUp to 20 basis points (0.2%) smallerPeer funds the shortfall if its remaining fee covers it
Explicitly cleared cap, or merchant-paid exact-token checksNo max-fee check when clearedUp to 20 basis points (0.2%) smaller; no larger-deposit searchPeer 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​

PropertyTypeRequiredDescription
amountstringYesPositive decimal string. Interpreted by quoteMode
quoteModeCheckoutModeTypeYes'exact-token' or 'exact-fiat', see below
enabledRailsstring[]NoNon-empty list of supported rails. Defaults to your account's rails. See rail-scoped availability
destinationChainIdstring | numberYesPayout chain, decimal only: 8453 or '8453' for Base. Hex ('0x2105') and signed strings ('+8453') are rejected with a TypeError before any request is made
destinationTokenstringYesPayout token, for example 'USDC'
destinationAddressstringYesPayout address
fiatCurrencystringNoFalls back to your configured default payment currency, then to USD
nearbyQuotesCountnumberNoSuggestions 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: amount is the order principal in USDC. In buyer pays mode this is the net USDC target.
  • CheckoutMode.EXACT_FIAT: amount is the order principal denominated in fiatCurrency.

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;
};
PropertyTypeDescription
availablebooleanWhether at least one quote on the checked rails can currently fill amount under your fee settings
quoteCountnumberHow many quotes matched
nearbySuggestionsobject | nullAlternative amounts. Always null when available is true

Each suggestion:

PropertyTypeDescription
suggestedAmountstringThe alternative order principal, in your requested quoteMode units. For SPLIT it excludes the buyer share of fees
percentDifferencestringSigned difference from your requested amount
railstringPayment rail that can fill it, for example 'venmo'
paymentAmountstringWhat the customer pays
paymentCurrencystringCurrency of paymentAmount
tokenAmountstringFor SPLIT, order principal in USDC after allocating quote costs between buyer and merchant by the saved fee share. Otherwise, USDC from the quote
conversionRatestringRate 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. nearbySuggestions is always null when available is true.
  • available: false does not guarantee suggestions. You can get nearbySuggestions: null or { below: [], above: [] }. Both mean there are no alternatives to offer, so check for null and for empty arrays.
  • nearbyQuotesCount is per direction. It caps below and above independently, so nearbyQuotesCount: 10 can return up to 20 suggestions.
  • The order and per-payment limits apply. An amount above 10,000 USDC fails with 400 AMOUNT_ABOVE_MAX, as it would at order creation. In exact-fiat mode 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 return available: 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: true is 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.

StatusMessageCause
400Invalid request: nearbyQuotesCount: Number must be less than or equal to 10A field failed validation. The message summarizes the field errors; complete detail is on fieldErrors and formErrors
400Merchant config not foundThe account has no checkout configuration yet
400Requested USDC amount must be at most 10000 USDCerrorCode AMOUNT_ABOVE_MAX: the amount, converted to USDC in exact-fiat mode, is above the 10,000 USDC order limit
401Missing or invalid API key
429Too many requestsRate limited. See the x-retry-after header below
502Unable to resolve exchange rate for <CUR>Exchange-rate lookup failed for that currency
502Quote provider unavailableQuotes could not be fetched. Retry
502Unable to price non-Base payout for quote availabilityThe non-Base payout could not be priced
500Unexpected 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:

HeaderValue
x-retry-afterSeconds until you may retry
x-rate-limit-remainingRequests left in the current window
x-rate-limiter-resets-atISO 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:

MessageMeaning
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 responseThe 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.