CLI command reference
Run peer-pay-cli COMMAND --help for usage and an example. A command group
such as peer-pay-cli webhooks lists its subcommands without contacting an API.
Unsupported options and conflicting request options fail before authentication or
network requests. On a terminal, missing required positionals and the required
flags below are filled in after resolving the profile. Without a terminal, missing
arguments retain their usage errors.
Filling in required arguments
A fully specified command runs without argument prompts. For example:
peer-pay-cli webhooks test # Select a webhook with Up/Down and Enter
peer-pay-cli webhooks test WEBHOOK_ID # Use this webhook immediately
peer-pay-cli payments create # Select ORDER_ID, then one of its enabled rails
peer-pay-cli payments cancel ORDER_ID # Select only a payment belonging to this order
Questions and selectors write to stderr, so --json on a terminal still allows
prompts while stdout remains JSON. Piped stdin never prompts. Esc, Ctrl+C, or Ctrl+D cancels
selectors. Headers show only the question label. Use Up/Down (or k/j, Ctrl+P/N)
to move, then Enter or Space to confirm a single choice. In multiple selectors,
Space toggles, a selects or clears all, and Enter confirms the set. Empty lists
fail with a command for creating an item.
Pickers use the command's authentication scope, including --management where
supported. Merchant order and merchant payment list pickers always use management
authentication: the signed-in session on hosted profiles, or the local API key.
Hosted management commands can therefore select IDs without a saved merchant API key.
Only required inputs are filled in. Optional filters (--status, --page,
--limit, --cursor, --kind) and other optional flags remain unprompted,
including crypto-simulate's optional --payment-id. The sole companion question
is team invite --role: when email is asked interactively and role was omitted,
the role selector defaults to MANAGER. Supplying email skips that selector too.
The registry uses these sources for every required positional:
| Commands | Missing input | Source / labels |
|---|---|---|
refunds get, refunds create, refunds cancel, orders get, orders cancel, orders resize, orders payments, payments create, payments cancel, payments recreate, payments extend, payments fulfill-sar, actions get, sandbox fail-payment, sandbox expire-payment, sandbox crypto-simulate, simulate | ORDER_ID | Merchant order list, GET /api/v1/merchants/me/orders; ID, status, requested USDC amount, or Awaiting amount for an open-amount order with no saved amount. A local profile reads the local mock, including for simulate. |
payments cancel, payments recreate, payments extend, payments fulfill-sar | PAYMENT_ID | After resolving ORDER_ID, GET /api/v1/merchants/me/orders/ORDER_ID/payments; ID, rail, status. |
disputes get | DISPUTE_ID | Dispute list, GET /api/v1/merchants/me/disputes; labels are DISPUTE_ID · STATUS · AMOUNT USDC · ORDER_ID. |
payments bridge | PAYMENT_ID | Merchant payment list, GET /api/v1/merchants/me/payments; ID, rail, status. |
webhooks update, webhooks delete, webhooks test, webhooks deliveries | WEBHOOK_ID | GET /api/v1/webhooks; URL, active state, ID. |
merchant switch | MERCHANT_ID | accessibleMerchants from GET /api/v1/auth/me; merchant name and access role. |
use | NAME | Saved profiles, with the active profile marked; no network request. |
team resend, team revoke, local accept-invite | INVITE_ID | Pending invitations from GET /api/v1/merchants/me/invites; email and role. |
team remove | USER_ID | merchantUsers from GET /api/v1/merchants/me; email (or ID when email is null) and role; excludes OWNER and locked (removalLocked) members. |
onboarding ack | STEP_ID | Shared ONBOARDING_STEP_REGISTRY, with completed steps marked from GET /api/v1/merchants/dashboard/me/onboarding. |
payouts get, payouts cancel, payouts timeline, payouts emails, payouts resolve-send, and every payouts simulate-* command | PAYOUT_ID | Payout list, GET /api/v1/payouts; labels are PAYOUT_ID · STATUS · AMOUNT USDC. |
payouts resolve-funding-issue | FUNDING_ISSUE_ID | Open funding issues from GET /api/v1/admin/cashouts/support?status=open; ID, kind, payout ID. Local profiles only. |
local retry | DELIVERY_ID | Select a webhook first, then GET /api/v1/webhooks/WEBHOOK_ID/deliveries; delivery ID, status, event. |
actions get | ACTION_ID | Text question; no action list endpoint exists. |
api | METHOD, PATH | Method selector (GET, POST, PUT, PATCH, DELETE), then a text path question. |
Required flags use these sources:
| Commands | Missing flag | Source |
|---|---|---|
payments create | --rail | After resolving ORDER_ID, read GET /api/v1/orders/ORDER_ID and select from that order's enabledRails, labelled with each rail name. Falls back to a text question only if the order cannot be read. |
quotes availability | --quote-mode | Backend QuoteModeSchema: exact-fiat or exact-token. |
quotes availability | --amount, --destination-chain-id, --destination-token, --destination-address | Text questions. |
webhooks create | --url | Text question. Omitted --events defaults to every event without prompting. |
team redeem | --token | Text question. |
sandbox fail-payment, sandbox expire-payment | --payment-id | Payments belonging to the resolved order, as above. |
sandbox crypto-simulate | --outcome | Backend SimulateSandboxCryptoOutcomeSchema choices. |
simulate | --event | Shared WebhookEventType, also used by local events. |
merchant tier | --tier | Backend SelfServeTierSchema choices. |
merchant ip-allowlist | --entries | Text question for comma-separated public IPs/CIDRs; skipped with --clear. |
fees settlement-amounts | --fee-payer | Shared FeePayer choices. |
fees settlement-amounts | --net-settled-usdc, --total-usdc-fee, --payment-amount, --currency-per-usd-rate | Text questions. |
refunds create | --seller-username | Text question. |
orders resize | --amount, --expected-amount | Text questions. |
payments fulfill-sar | --tx-id | Text question. |
merchant bridge-signer-status | --signer-id | Text question. |
orders support-links | --order-ids | Text question for comma-separated IDs. |
merchant concierge | --contact-email, --answers | Email text question, then nine intake questions with fixed-option selectors where available. |
merchant concierge-inquiry | --contact-email, --answers | Email text question, then nine intake questions with fixed-option selectors where available. |
merchant logo | --file | Text question for the file path. |
team invite | --email | Text question, then optional role selector from CreateMerchantInviteSchema. |
credentials export | --output | Text question for the output path. |
payouts create | --idempotency-key, --email, --amount, --funding-chain-id, --funding-token | Text questions. With --data, supply --idempotency-key explicitly. |
payouts configure-local | --rails | Text question for comma-separated rail ids. |
payouts simulate-funding, payouts simulate-payout-bridge | --status | Funding status selector: success, failure, refund, pending. |
payouts simulate-credential | --status | Credential status selector: active, inactive, missing. |
payouts simulate-payment | --step | Text question: signaled, replaced, expired, pruned or fulfilled. |
payouts simulate-wallet-balance | --amount | Text question for the wallet's USDC balance. |
local settings, local admin, payouts resolve-funding-issue | --data | Must be supplied explicitly; raw JSON is never prompted. |
On commands that accept --data, supplying it skips the other required flag
questions and validation requirements; missing positionals still prompt first.
This includes payments create, team redeem, and quotes availability.
local settings, local admin and payouts resolve-funding-issue still require explicit --data.
orders create amount, fiat and open-amount inputs remain explicit. Optional --quote-limit
and --nearby-quotes-count never prompt. merchant update preserves the server's
current name when --name is omitted, without prompting.
Choice fetches only use existing GET routes. Paginated lists show the API's default
page; supply an ID directly to address an item outside that page.
Global options
| Option | Meaning |
|---|---|
--profile NAME | Explicit saved profile; takes precedence over --local. |
--local | Target the local mock profile. |
--config FILE | Credentials file. Overrides PEER_PAY_CONFIG and the default ~/.config/peer-pay/config.json. |
--json | Full JSON output. Automatically enabled when stdout is piped or redirected. |
--show-secrets | Reveal otherwise-redacted API keys, order tokens, and signing secrets; webhooks create and merchant rotate-key already show newly issued credentials in full. Does not apply to serve, profiles, or handoff. Prefer credentials export for application setup. |
--help | Show help for the selected command. No login or running server is required. |
--version | Print the installed CLI version. |
Options can appear before or after the command. Success exits with code 0;
errors go to stderr and exit with code 1, including after interactive prompts.
Both streams are flushed before exit. serve stays running until stopped; startup
failures exit with code 1. Interrupted client commands release their
profile lock before exiting with 130 (SIGINT/Ctrl+C) or 143 (SIGTERM). Machine-readable errors contain
success: false and message. API failures also retain errorCode when present,
responseObject, and the HTTP statusCode. API command JSON retains the backend's
success, message, responseObject, and statusCode fields. webhooks create and merchant rotate-key show newly issued credentials in full
without --show-secrets, save them in the profile, and print a one-time notice to
stderr so JSON stdout stays clean. All other commands keep credentials redacted
unless --show-secrets is supplied.
Profiles
After you log in, commands target your Peer Pay account; add --local to target the mock, or peer-pay-cli use local. Selection is --profile NAME > --local > saved activeProfile > local if it exists. With no profile, run peer-pay-cli login or peer-pay-cli serve. Serve always owns local and never changes the active profile.
Sessions
Email sessions refresh only for management requests when the saved access token is missing or expired, or once after a 401. API-key commands and credentials export do not contact Privy. A management request performs at most one refresh.
Request input
--data accepts inline JSON or a file prefixed with @. Quote inline JSON
so your shell passes it unchanged:
peer-pay-cli merchant settings --profile live --data '{"defaultPaymentCurrency":"USD"}'
peer-pay-cli merchant settings --profile live --data @settings.json
Use either --data or individual body options; combining them is an error.
Amounts and chain IDs in JSON use decimal strings. --events takes comma-separated
webhook event names. IDs in examples are placeholders for IDs returned by the API.
Interactive API errors report HTTP STATUS: MESSAGE [ERROR_CODE] (omitting the
code suffix when absent), using the API message when available or HTTP status
text for non-JSON responses (Request failed if the status text is empty). With --json or non-TTY output, stderr contains
the failure envelope as JSON. Malformed --data
JSON reports an input hint; unrelated JSON parse errors retain their own message.
Successful bodyless HTTP 204 responses return responseObject: null.
Local integration status identifies webhook deliveries with no HTTP response and
asks you to check the webhook URL and delivery details.
Getting started
With no command in a terminal, the CLI offers sign-in, merchant setup, coding-agent handoff, local sandbox, remote sandbox (item 5), status, and exit. Without a terminal it prints top-level help to stderr and exits 2.
Run peer-pay-cli serve to start the local sandbox. Local state is in memory unless
--state FILE is set; Ctrl+C saves persistent state and stops the server.
serve binds only to 127.0.0.1; --port defaults to 4020.
--api-key, or the PEER_PAY_API_KEY environment variable, sets the local test
key; otherwise a key is generated. With --state, a key that differs from the
persisted one is rejected with --api-key differs from the persisted state key.
Login derives a profile name from the merchant and makes it active. Use --profile NAME to name it explicitly. It sends an
email code unless --token-stdin is supplied. --api-url selects a backend
origin (default https://api.pay.peer.xyz); --merchant-id selects an accessible
merchant. The CLI gets the public Privy client ID and native app identifier from
Peer Pay automatically when configured by the server. --client-id and
--app-identifier <id> override their respective values. Both are saved with the
profile and reused when refreshing the session.
Do not place access tokens in command arguments.
Setup accepts --file setup.json, --dry-run to preview, or --yes to
apply without confirmation. --dry-run and --yes cannot be combined.
Destination chain IDs appear as strings in setup dry-run output.
See Merchant setup. Logout removes local credentials only.
serve
Start the local mock backend.
Open the server's root URL in a browser to see a landing page with the local ledger (merchant and counts), quickstart commands, endpoint links, documentation links, and the command reference. The page never includes credentials. Every other unknown path returns a JSON 404.
peer-pay-cli serve [options]
Options: --port, --state, --api-key.
Ctrl+C (SIGINT) or SIGTERM closes all HTTP connections, including idle keep-alive
connections and unfinished requests, then saves state and releases the
<state>.lock file. A hard kill such as SIGKILL bypasses cleanup; remove a stale
lock only after confirming its server process has exited.
Example:
peer-pay-cli serve --state ./peer-pay.json
Local commands use the API key printed by serve. login --profile local is
rejected by the CLI; direct POST /api/v1/auth/login returns an envelope with
the local owner fixture profile with both wallet addresses already present; it does not authenticate with Privy. Public
GET /api/v1/auth/cli-config returns {appId:"local-mock"} as a placeholder.
login
Sign in or create a merchant account with email. The CLI checks API login configuration before requiring a terminal for email verification.
The result reports success: true, profile, and activeProfile after saving the authenticated profile. Without --profile, login reuses a profile with the same API URL and merchant ID, or derives a lowercase name using letters, numbers, underscores, and hyphens (up to 32 characters). An empty name uses merchant- plus the first eight merchant ID characters; name collisions receive a numeric suffix. The name local remains reserved for the mock.
Owner wallets are provisioned automatically by the server on first sign-in,
and later owner sign-ins retry missing wallets, regardless of which client is used.
Existing wallet addresses are preserved. The CLI reports walletsProvisioned: true
when both v1 addresses are present in the login response. If either is missing,
sign-in still succeeds with walletsProvisioned: false and a message pointing
to the manual retry command: peer-pay-cli merchant provision-wallets --profile NAME. For managers and cashiers, walletsProvisioned is null. The CLI does not make a separate provisioning request during login.
Login saves only non-empty API keys. A cashier-masked empty key is not stored,
and the CLI clears that profile's cached API key, order tokens, and webhook
secrets.
After login, only merchant rotate-key replaces the saved API key. Generic
API responses containing apiKey do not update credentials.
peer-pay-cli login [options]
Options: --email, --referral-code, --api-url, --merchant-id, --client-id, --app-identifier, --token-stdin, --transfer-token.
--transfer-token sends the token from a merchant-account-transfer email. For an account with no merchant yet, sign-in then returns a routing state instead of creating a merchant:
PENDING_OWNERSHIP_TRANSFER: login saves a profile without a merchant (namedpending-transfer, or--profile NAME) and makes it active. Runpeer-pay-cli transfer accept --profile NAME --token TRANSFER_TOKENnext. Without--transfer-token, an email with a pending transfer gets the same state, and the message asks you to open the link from the email.OWNERSHIP_TRANSFER_EMAIL_MISMATCH: the transfer is for another email, shown masked (a•••@example.com). No profile is saved.OWNERSHIP_TRANSFER_UNAVAILABLE: the transfer was withdrawn, expired, or the token is unknown. No profile is saved. Sign in again without--transfer-token.
An account that already has a merchant signs in normally; accept the transfer with transfer accept.
Example:
peer-pay-cli login --email owner@example.com
setup
Configure your hosted merchant with a guided setup or JSON file. In a terminal, setup offers sign-in first when the profile is missing, local, or has no management session, then continues the guided questions. Without a terminal, sign in using peer-pay-cli login first; run peer-pay-cli setup --file setup.json --dry-run to preview or peer-pay-cli setup --file setup.json --yes to apply.
--dry-run validates the input before reading current state, makes no changes,
and does not provision a sandbox. The preview omits the onboarding checklist;
guided pricing-plan selection is deferred until you choose to apply. A setup
file can include an explicit tier.
Setup checks your current role before applying changes. Managers can apply settings
and enable the sandbox; owner-only onboarding reads and guided pricing selection
are skipped. The successful result includes onboarding: 'skipped: requires owner',
and a requested handoff still runs. Cashiers cannot run setup.
A logo is optional in both guided setup and setup JSON files. Saving the integration path completes the preceding profile, industry, and payment-method steps.
In a setup JSON file, omitting webhook.events defaults to every webhook event.
peer-pay-cli setup [options]
Options: --file, --yes, --dry-run, --harness, --project, --launch.
Use --harness codex|claude|opencode|gemini|prompt to prepare a handoff after
setup is applied. Add --launch to start the installed agent in --project DIR
(default: current directory). A cancelled setup does not create a handoff.
Use --dry-run separately to preview settings.
Example:
peer-pay-cli setup --profile live
Launched harnesses receive PEER_PAY_CONFIG but do not inherit
PEER_PAY_API_KEY from the CLI environment.
handoff
Prepare integration instructions or launch your coding agent.
peer-pay-cli handoff [options]
| Option | Meaning |
|---|---|
--harness NAME | codex, claude, opencode, gemini, or prompt. Prompts in a terminal; defaults to prompt in scripts. |
--project DIR | Repository to open in the agent. Defaults to the current directory. |
--output FILE | Write instructions to a new file. Defaults to a unique file beside the CLI config, under handoffs/. Existing files are never overwritten. |
--launch | Start the selected installed agent and wait for it to exit. Requires a harness other than prompt. |
Example:
peer-pay-cli handoff --harness codex --project ./my-store --launch
Supported agents are Codex, Claude Code (claude), OpenCode, and Gemini CLI.
Install and sign into your chosen agent separately; its executable must be on
PATH. The CLI starts an interactive session with the agent's normal permissions.
In a terminal, missing --harness opens an arrow-key chooser (Up/Down or k/j, Enter or Space to choose, Escape, Ctrl+C, or Ctrl+D to cancel), missing --project prompts with the current directory as default, and omitting --launch asks whether to launch the selected agent. Prompt-only never launches. Without a terminal, no questions are asked: omitted harness defaults to prompt, the project defaults to the current directory, and launch requires --launch. Handoff uses either the selected hosted profile or local profile; with neither, run peer-pay-cli login or peer-pay-cli serve.
Use --harness prompt to give the instructions to another coding tool yourself.
The instructions identify your selected profile and config path, describe the SDK
and webhook integration, and outline local and hosted sandbox tests. They contain
no API keys, tokens, or signing secrets. The agent can use the CLI with your selected
profile by passing --profile NAME on every merchant command. Handoff does not
change the active profile, including when invoked with --profile NAME. Local
tests use peer-pay-cli serve --profile local and --profile local on client
commands. --local is an alternative only without --profile, which takes
precedence. The agent is instructed to export credentials directly into an ignored private
file and ask before changing merchant settings or deploying.
peer-pay-cli handoff --profile live --harness claude --project ./my-store --launch
peer-pay-cli handoff --harness opencode --project ./my-store --launch
peer-pay-cli handoff --harness gemini --project ./my-store --launch
peer-pay-cli handoff --output ./peer-pay-instructions.md
A missing executable or nonzero agent exit makes the command fail; the error
includes the saved prompt path so you can resume manually. The agent receives
PEER_PAY_CONFIG for the selected credentials file. Its own terminal output is
interactive even when the CLI uses --json; omit --launch for JSON-only scripts.
status
Check integration verification for the selected profile.
peer-pay-cli status
Example:
peer-pay-cli status --profile live-sandbox
profiles
List saved profiles without credentials.
peer-pay-cli profiles
Example:
peer-pay-cli profiles
logout
Remove a saved profile from this computer.
peer-pay-cli logout
Example:
peer-pay-cli logout --profile live
Orders
Split-fee pricing accepts principal drift in either direction up to the greater
of SPLIT_FEE_TOLERANCE_USD (default 1) and the expected principal multiplied
by SPLIT_FEE_TOLERANCE_BPS (default 50, or 0.5%). The percentage is of the
order principal, not the fee. Fiat, crypto, and Apple Pay use the same policy.
Settled split-fee orders complete with a residual up to the larger of that
tolerance and the order completion threshold below; actual paid amounts, fees,
and residuals remain recorded.
Every order completes once its remaining amount is at most its completion
threshold: the greater of ORDER_COMPLETION_THRESHOLD_USDC (default 1 USDC) and
requestedUsdcAmount × ORDER_COMPLETION_THRESHOLD_BPS / 10_000 (BPS default 100, or 1%).
Both the floor and the percentage are current settings applied on each rollup;
changing either applies to every order on its next rollup, including orders
loaded from a saved state file. With the defaults, a 20 USDC order completes at
1 USDC or less remaining, and a 1,000 USDC order completes at 10 USDC or less remaining.
Hosted environments can override these defaults.
The local simulator reads these environment variables when it starts, using
the same defaults and validation as the hosted API. Hosted commands use the
server's configuration. Dollar values must be nonnegative with at most six
decimal places; basis points must be an integer from 0 to 10000. Set both split
variables to 0 to require exact split pricing.
orders create requires --amount, --fiat-amount with --fiat-currency,
--open-amount with --currency, or --data. The optional
--idempotency-key belongs to your business order: retry with the same key and
amount to return the same order. USDC retries compare the order's current
requestedUsdcAmount: after resizing from 10 to 20, retrying 10 returns HTTP 409
IDEMPOTENCY_KEY_CONFLICT, while 20 replays. Fiat retries compare the original
requestedFiatCurrency and requestedFiatAmount in metadata, even after resizing;
a USDC retry for a fiat order conflicts. Decimal comparisons truncate to six
places. A replay returns a null order token; the CLI retains the token from its
original creation response. Whitespace around USDC and fiat decimal inputs is
trimmed before conversion; fiat metadata stores the trimmed amount.
Both order-creation endpoints return Order created for a new order and
Order already created for an idempotent replay.
Open-amount orders (--open-amount, or openAmount in --data) have no amount
when they are created: the buyer types it in checkout. --currency is the
order's currency, which --min, --max and --presets are in (the buyer
types in checkout's selected currency, and the limits convert to it); --min,
--max and comma-separated --presets (1 to 6) are optional and take at most
2 decimals. The local simulator always accepts open amounts; hosted commands
return the API's HTTP 400 OPEN_AMOUNT_DISABLED until open amounts are enabled
for that environment. The order starts with amountMode: "OPEN", null
requestedUsdcAmount and remainingUsdcAmount, and amountVersion: 0. It cannot be combined with an
amount, inPersonCheckout: true or dynamicOrdersEnabled: true. Invalid input
returns HTTP 400 OPEN_AMOUNT_INVALID with a message that names the field, for
example openAmount.presets: 5.00 is outside the allowed range 10.00 to 100.00 USD.
The effective range is the tightest of --min / --max and the local settings
minOrderAmountUsdc and maxOrderAmountUsdc, converted at the local fixture
rate. As for hosted sandbox merchants, the merchant's order-size limits do not
apply. A --max that converts above maxOrderAmountUsdc returns
openAmount.maxAmount: must convert to at most ${max} USDC, and a preset above
the range is outside it. An idempotent retry must repeat the same normalized openAmount. A fixed
request that reuses the key of an open order, or an open request that reuses the
key of a fixed order, returns HTTP 409 IDEMPOTENCY_KEY_CONFLICT.
On an open-amount order, GET /api/v1/orders/ORDER_ID/platform-quotes returns
platforms: [] with amountRequired: true until an amount is saved. While the
amount can change, every response also carries openAmountDisplay: the
effective range and presets in fiatCurrency at the local fixture rates, exact
in the order's currency (presets outside the range left out) and in whole units
in any other (minimum up, maximum down, presets to the nearest unit inside the
range), with currencyPerOrderUnit. An empty range has no presets. Its bounds
and currencyPerOrderUnit are null and its presets empty when either currency
has no fixture rate. Add amount (in
fiatCurrency) to price a draft: the response adds amountUsdc and applePay
(chargeUsdcAmount, eligible), and nothing is saved.
payments create --amount A --amount-currency C --expected-amount-version V,
with --currency C on a fiat rail, saves a new amount, typed in C, with the
payment. V must equal the order's
amountVersion. The order's amounts and fiat metadata (in C) are updated,
amountVersion goes up by one, and ORDER_AMOUNT_SET is sent before
PAYMENT_CREATED. Repeating the saved amount in the same currency takes the
normal path and sends no event; the same number in another currency is a
different amount. A different amount returns:
- HTTP 409
AMOUNT_LOCKEDwith{ reason }while a payment can still complete: aCREATEDorEXPIREDpayment, a cancelled Zcash payment, a failed Relay payment the hosted API can still reopen, or any settled payment. A draft quote with anyamountreturns the same while the order is locked; - HTTP 409
AMOUNT_CONFLICTwhen the version is stale; - HTTP 400
AMOUNT_OUT_OF_RANGEwith{ effectiveMin, effectiveMax, currency }outside the effective range converted to the typed currency (the body's range is in that currency), including an amount abovemaxOrderAmountUsdcon an order without--max; - HTTP 400
INTENT_ABOVE_MAXon a fiat rail when the amount plus buyer-paid fees would sign more thanmaxOrderAmountUsdc, even inside the range (see payments create). Draft quotes leave such rails out. - HTTP 400 when
amount,amountCurrencyandexpectedAmountVersionare not sent together, or whenamountCurrencyis not a fiat code. A draft quote that sendsamountwith a tokenfiatCurrency, such asUSDC, returns the same. - HTTP 400 on a fiat rail when
fiatCurrencyis notamountCurrency. An omittedfiatCurrencymeans USD, so a fiat payment with a non-USD amount must sendfiatCurrencyin that currency. Crypto and Apple Pay payments are not checked.
amount on a fixed order returns HTTP 400 AMOUNT_NOT_ALLOWED, and a payment
on an open order with no saved amount and no amount returns HTTP 400
AMOUNT_REQUIRED. Resizing an open-amount order returns HTTP 400
RESIZE_NOT_SUPPORTED. The hosted order read adds openAmount.effectiveMin,
effectiveMax and savedInput, and amountLock. Like the hosted API, the
local read returns effectiveMin and effectiveMax as null once a payment
locks the amount or the order is fulfilled or cancelled. The local server does not
rate-limit draft quotes; the hosted API does, for sandbox merchants too. Like the
hosted API for sandbox merchants, it offers no nearby amounts and skips the
monthly volume cap.
The local platform minimum defaults to 1 USDC to match production and applies
after USDC truncation or fiat conversion. Set minOrderAmountUsdc through
local settings to simulate stricter environments. Smaller amounts return HTTP 400
AMOUNT_BELOW_MIN with Requested USDC amount must be at least ${min} USDC,
where ${min} is the configured local minimum.
The local platform maximum defaults to 10,000 USDC, the hosted API's
per-order limit, and is checked on the same amount right after the minimum. Set
maxOrderAmountUsdc through local settings to change it. Larger amounts return
HTTP 400 AMOUNT_ABOVE_MAX with Requested USDC amount must be at most ${max} USDC,
where ${max} is the configured local maximum, so a fiat amount is refused when
its converted USDC amount is above it. Both checks precede
DYNAMIC_ORDERS_DISABLED. Fiat amounts
that round to 0.00 USDC are rejected first with HTTP 400 AMOUNT_NON_POSITIVE
and Converted fiat amount must be greater than 0 USDC.
Merchant order-size limits (minOrderSizeUsdc and maxOrderSizeUsdc) are accepted
and stored through the admin config endpoint but are not enforced on order
creation. The simulated merchant is always a sandbox merchant, matching the hosted
API's sandbox behavior. When either limit is supplied, admin config writes reject
a minimum greater than the maximum with
Minimum order size cannot exceed maximum order size. A minimum above the
local settings maxOrderAmountUsdc is rejected with
Minimum order size cannot exceed the 10000 USDC platform maximum, naming the
current setting; a maximum above it is accepted. Unrelated admin PATCHes
do not revalidate stored limits. The platform-wide minimum and maximum above still apply.
Per-order enabledRails overrides replace the merchant selection, so an override
such as cashapp can be used even when absent from the merchant list.
EXCLUSIVE_SAR and in-person orders remove non-SAR fiat rails such as Revolut,
while retaining SAR and crypto rails. Runtime-disabled rails are filtered from
REST responses, webhook orders, and admin recent orders but retained in the stored
selection for re-enablement. If no eligible
rails remain, creation returns HTTP 400 NO_ELIGIBLE_PAYMENT_RAILS with
No eligible payment rails for this merchant. This check precedes in-person rail
eligibility, amount resolution, dynamic-order eligibility,
and payout validation. Apple Pay counts as eligible only when enabled and available
and the destination resolves with a valid address, for any payout token. With Apple
Pay as the only available rail (including when accompanying fiat rails are
runtime-disabled), an invalid destination address returns NO_ELIGIBLE_PAYMENT_RAILS
before any below-minimum amount or in-person rail error. This matches the backend,
including its rails error for an invalid address. A valid destination passes
this gate; an in-person order then returns IN_PERSON_NO_ELIGIBLE_RAIL, or a
below-minimum amount returns AMOUNT_BELOW_MIN.
In-person orders (POST /api/v1/merchants/me/orders with inPersonCheckout: true) also
require an offerable fiat rail eligible for seller automated release after SAR
and runtime filtering. Crypto-only selections return HTTP 400
IN_PERSON_NO_ELIGIBLE_RAIL with
In-person checkout requires at least one seller automated release eligible fiat rail.
Payout overrides in --data validate destinationAddress against
destinationChainId: EVM, Solana, and Tron use the backend's address validators.
After the earlier eligibility and amount checks pass, invalid addresses return
HTTP 400 DESTINATION_INVALID with
Destination address format does not match destination chain. The local
merchant has provisioned EVM and Solana wallets and derives its Tron address
from the EVM wallet. Orders use the backend merchant wallet resolver to select
the wallet for the destination chain when no explicit address is supplied,
including for Solana chain overrides and merchant payout defaults.
After address validation, payout configuration failures return HTTP 400
PAYOUT_CONFIG_INVALID: Unsupported payout destination chain,
Unsupported payout destination token for chain, or
Merchant payout configuration requires sweep support for non-Base destinations.
Solana USDC uses the provisioned Solana wallet by default and requires
sweepEnabled: true. Tron creation
requires USDT and sweep support, including when using the derived Tron address;
Tron USDC is rejected. Base USDC does not require sweep support.
Setting dynamicOrdersEnabled: true cannot override a disabled merchant setting:
creation returns HTTP 400 DYNAMIC_ORDERS_DISABLED with
dynamicOrdersEnabled cannot be enabled: dynamic orders are disabled for this merchant.
Omitting the field inherits the merchant setting; false disables it for the order.
Resize requires a dynamic order and both --amount (new total) and
--expected-amount (current total). Resize and payment cancellation need an
order token saved by this CLI profile. Hosted order cancellation requires the
signed-in merchant's management permissions. List commands use backend pagination
defaults; use their filter flags and --page / --limit for subsequent pages.
orders create
The local minimum defaults to 1 USDC and the local maximum to 10,000 USDC; set
minOrderAmountUsdc and maxOrderAmountUsdc with local settings to change them.
Decimal inputs are trimmed. Order rails override merchant rails,
subject to SAR and runtime eligibility; in-person orders need an eligible fiat
rail. Payout chains and tokens must be supported; non-Base-USDC payouts require
sweep support. Dynamic orders require merchant enablement. See the rules above
for validation details.
Create an order with --amount (requestedUsdcAmount), --fiat-amount plus
--fiat-currency, or --open-amount with --currency and optional --min,
--max and --presets (openAmount). The three amount modes are exclusive.
--data is an exclusive raw JSON body alternative for all commands with body flags.
--notes accepts a JSON object string or @file. --enabled-rails is a comma
list. --dynamic-orders-enabled takes true or false. Use --management
for POST /api/v1/merchants/me/orders and optionally --in-person true|false;
otherwise creation uses POST /api/v1/orders. Cashiers must use in-person
checkout and cannot override payout destinations, rails, or fee settings.
--fee-payer accepts MERCHANT, PAYEE, or SPLIT. An explicit SPLIT
override requires --buyer-fee-share-bps, an integer from 0 to 10000 in steps of 1000 (5000 = 50%).
Omit both flags to inherit the merchant settings. In split mode the amount is
the order principal; checkout adds the buyer share of fees, and settlement deducts
all fees. The order retains its original buyer share after settings change.
See split payment fees.
Hosted creation returns HTTP 409 MERCHANT_OWNERSHIP_CHANGED when the merchant account changed
owners while the order was being created. Nothing was saved: retry the same request,
with the same --idempotency-key, and the order uses the new owner's payout wallet.
The local simulator never returns this code, because it has no concurrent ownership transfer.
peer-pay-cli orders create [options]
Options: --amount, --fiat-amount, --fiat-currency, --open-amount, --currency, --min, --max, --presets, --idempotency-key, --destination-address, --destination-token, --destination-chain-id, --fee-payer, --buyer-fee-share-bps, --enabled-rails, --dynamic-orders-enabled, --success-url, --cancel-url, --notes, --management, --in-person, --data.
Example:
peer-pay-cli orders create --amount 10 --idempotency-key order-123
peer-pay-cli orders create --amount 100 --fee-payer SPLIT --buyer-fee-share-bps 400
peer-pay-cli orders create --open-amount --currency USD --min 10 --max 2000 --presets 25,50,100
orders get
Use --management for GET /api/v1/merchants/me/orders/:orderId with the signed-in session.
Read an order and its payments. The hosted aggregate includes
order.applePayChargeUsdcAmount: the current remaining USDC balance grossed up
for Apple Pay fees in PAYEE mode or increased by the buyer share of fees in SPLIT
mode, or null when Apple Pay is not
enabled or pricing cannot be resolved. Pricing failures log a warning without
preventing the order read; payment creation still rejects invalid pricing.
Local simulation uses the same two-pass gross-up as the API and the
order's frozen fee configuration. Checkout hides Apple Pay when this charge or
requestedUsdcAmount is above 2,500 USDC, and payment creation rejects Apple
Pay orders and funding quotes above that cap with a 422.
peer-pay-cli orders get ORDER_ID
Options: --management.
Example:
peer-pay-cli orders get ORDER_ID
orders list
List rows include settledPaymentCount (all settled payments, including repeated
rails), settledPaymentRails (distinct settled rails, oldest payment first), and
netSettledUsdcAmount (post-fee USDC total; null when nothing has settled or any
settled payment has no recorded net). bridgeInfo describes the latest conversion
attempt of the newest settled payment with an attempt, or is null when absent.
These fields match the merchant order list in local mode.
List merchant orders with first-class filter and pagination flags:
peer-pay-cli orders list --order-id ORDER_ID
peer-pay-cli orders list --display-status ACTIVE --chargeback-status NONE --limit 10 --page 1
peer-pay-cli orders list --search "Cash App"
orderId is a case-insensitive substring match. search (at least three
characters) takes precedence over orderId and matches
order IDs, notes, metadata, payment rails/display names, payment IDs, recipient
handles/addresses (payTo), payment-app transaction IDs (railTransactionId),
settlement hashes (fulfillTransaction), and rail identifiers without treating
% or _ as wildcards. Crypto token/chain aliases match crypto payments.
Plain non-negative decimals, optionally prefixed with $ (for example $20 or
20.00), also match the requested fiat amount in order metadata or requested
USDC amount by exact numeric equality. Amount searches follow the same
three-character minimum, so use $20 or 20.00, not 20. Hosted commands use
the API search. The local simulator applies the same rules, except payment-app
transaction IDs, which the simulator never records.
Search considers the newest 5,000 matching orders before other filters.
status accepts CREATED, PARTIALLY_FULFILLED, FULFILLED, or CANCELLED.
displayStatus overrides status and selects CREATED orders with: no CREATED
or EXPIRED payments (CREATED), any CREATED payment (ACTIVE), or an EXPIRED
payment and no CREATED payment (EXPIRED). Failed/cancelled attempts do not
make an order active or expired.
chargebackStatus is NONE, PARTIALLY_CHARGEBACKED, or CHARGEBACKED, derived
from settled payments: none charged back, a mix of charged and uncharged, or
all charged back. It combines with display/status filters. All filters apply
before totals and pagination; results are newest first. Page defaults to 1;
limit defaults to 20 and has a maximum of 100.
peer-pay-cli orders list
Options: --status, --chargeback-status, --display-status, --order-id, --search, --page, --limit.
Example:
peer-pay-cli orders list
orders cancel
Cancel a CREATED order that has never had a payment. Success returns HTTP 200
with message Order cancelled and the mapped order directly in responseObject
(no nested order field). Repeated cancellation returns 409.
peer-pay-cli orders cancel ORDER_ID
Example:
peer-pay-cli orders cancel ORDER_ID
orders resize
Resize an unpaid CREATED dynamic order with no CREATED or EXPIRED payments, using its cached order token.
peer-pay-cli orders resize ORDER_ID [options]
Options: --amount, --expected-amount.
The expected amount must match the current total, and the new amount must differ.
Amounts round to six decimals. Invalid amounts return 400; ineligibility returns
409 with RESIZE_CONFLICT and Order cannot be resized. The local sandbox uses
the local settings minOrderAmountUsdc floor (default 1 USDC to match
production) and ignores merchant-specific sandbox size limits. A total above the
local settings maxOrderAmountUsdc (default 10,000 USDC, like the hosted API)
returns 400 AMOUNT_ABOVE_MAX with Requested USDC amount must be at most ${max} USDC
before any eligibility check. FAILED and CANCELLED
payments do not block resizing; any payment row blocks order cancellation.
Cancelling a non-CREATED order returns 400, except an already CANCELLED order
returns 409.
Example:
peer-pay-cli orders resize ORDER_ID --amount 15 --expected-amount 10
Payouts
Payouts are owed in USDC on Base. You fund them through a Relay deposit address with a supported EVM token or Bitcoin; Solana, Tron and Zcash cannot fund payouts. Customers can take any supported payout coin and network. See the Payouts API for the money rules, statuses, and error codes. These commands use the API key.
payouts create
Create a payout for a customer email and get its funding deposit address.
peer-pay-cli payouts create [options]
Options: --email, --amount, --funding-chain-id, --funding-token, --refund-address,
--merchant-reference, --return-url, --idempotency-key, --rails RAIL,RAIL, --data.
--amount is the USDC owed to the customer, before their chosen crypto conversion. It must be a
whole multiple of the merchant's payout step (local default 100) and at most 1000 USDC. A step
mismatch returns 400 PAYOUT_NOT_STEP_MULTIPLE (Payout amount must be a multiple of {step} USDC); an amount above the maximum that passes
the step check returns 400
PAYOUT_ABOVE_MAX ("Payout amount is above the payout maximum"). Lower the local step
with payouts configure-local --payout-step.
Use --data or individual request flags, except --idempotency-key, which works with either.
Mixing them stops with "Use --data or individual request options, not both."
--funding-token is
the token address you fund with on --funding-chain-id, and the optional --refund-address receives
refunds if the deposit cannot be converted. Omit it to refund the sending wallet.
Set it for exchange withdrawals: Relay does not automatically refund exchange wallets. --idempotency-key is sent as the
Idempotency-Key header, 8–128 letters, digits, _ or -, and is required even with
--data. The first response has idempotentReplay: false. Rerunning with the same key and body
returns the existing payout with status 200, the current checkoutUrl, and idempotentReplay: true;
a different body returns 409 IDEMPOTENCY_KEY_CONFLICT. The link token is derived from the
payout id and the current signing secret, and checked against it on every request. The local
server uses a fixed secret, so a local link stays the same. Never send funds after
funding.quoteExpiresAt; create a new payout with a new key.
For Bitcoin funding, pass --funding-chain-id 8253038 --funding-token bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8. --refund-address is then required and must be a Bitcoin address you control (bc1…, 1… or 3…); a missing or invalid one fails before any request, with the API's message. The local server prices BTC at a fixed $100,000 per BTC with the 2% volatile buffer, capped at 10 USDC, and gives the payout a random bc1q… deposit address. With the default 0% Peer fee and zero merchant fee, the example below asks for 0.00102 BTC: the buffer takes 100 USDC to 102, with no fee gross-up.
peer-pay-cli payouts create --local --email customer@example.com --amount 100 --funding-chain-id 8253038 --funding-token bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8 --refund-address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 --idempotency-key payout-btc-001
Example:
peer-pay-cli payouts create --email customer@example.com --amount 100 --funding-chain-id 8453 --funding-token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 --refund-address WALLET_ADDRESS --idempotency-key payout-001
--rails optionally limits this payout to a non-empty comma-separated list such as
venmo,relay_8453,near_intents_133701. It never adds a rail the merchant or Peer turned off. Unavailable
requested rails are dropped; if none remain, create returns 422
PAYOUT_REQUESTED_RAILS_UNAVAILABLE (“None of the requested payout methods are available right now”).
An empty merchant rail list returns 403 PAYOUTS_DISABLED ("Payouts are not enabled for this merchant");
if Peer has disabled every merchant rail, create returns 422 PAYOUT_NO_RAILS_AVAILABLE
(“None of this merchant’s payout methods are available right now”).
Unknown ids, crypto, duplicates and an empty rails array fail before any request with the
API’s validation message (the API itself answers 400); an empty --rails flag stops with
“--rails is required”. The API normalizes rail order: a reordered list replays with the same key;
different rails conflict. A request without rails replays exactly as before (same key, same body).
--data can supply the same rails array.
payouts get
Show one payout with funding progress and status.
peer-pay-cli payouts get PAYOUT_ID
Another merchant's payout returns 404 PAYOUT_NOT_FOUND.
Example:
peer-pay-cli payouts get PAYOUT_ID
payouts list
List payouts, newest first, filtered by email, status or reference.
peer-pay-cli payouts list [options]
Options: --email, --status, --merchant-reference, --page, --limit.
--status accepts AWAITING_FUNDING, FUNDED, READY, LISTING, PAYING, SETTLED, CANCELLED,
or EXPIRED. --merchant-reference matches exactly. --limit is 1–100
(default 20). The response is { items, page, limit, total }.
Example:
peer-pay-cli payouts list --email customer@example.com
payouts cancel
Cancel a payout that is still awaiting funding.
peer-pay-cli payouts cancel PAYOUT_ID
Only an AWAITING_FUNDING payout with nothing received can be cancelled; otherwise the
command returns 409 PAYOUT_NOT_CANCELLABLE with the current status and fundingStatus.
Hosted cancels also return 409 PAYOUT_FUNDING_DETECTED when funds are already on the way.
Example:
peer-pay-cli payouts cancel PAYOUT_ID
payouts configure-local
Change the local simulator merchant's payout settings; every platform is on by default.
peer-pay-cli payouts configure-local [options]
Options: --rails, --fee-bps, --payout-step, --support-url.
Local profiles only. It runs the same settings schema as the dashboard's Settings → Payouts tab
(PUT /api/v1/merchants/me/payout-settings), which the CLI can't call: those routes need a
dashboard sign-in. --rails is required: a comma-separated list of any of venmo, cashapp, paypal,
zelle, revolut, chime, the supported relay_<chainId> network ids and near_intents_133701, stored in
rail order. For example --rails venmo,relay_8453,near_intents_133701.
Opening an existing state file does not append ZEC to its saved rails. Use configure-local --rails
with the full desired list, including near_intents_133701, to enable ZEC for new local payouts.
Existing payouts keep their stored rails.
The local NEAR simulator prices 1 ZEC = 100 USD, enforces a 1.58 USDC minimum, and
uses a 20-minute refund deadline, with sendBy 5 minutes before it. These are local
fixtures; local settings rates does not change them. Live minimums come from NEAR. Zcash recipients must be transparent t1… or t3… addresses.
The retired crypto id is refused with 400. The local merchant starts with every
platform on, so payouts work before this command ever runs. The local Peer fee defaults to
peerFeeBps: 0 (0%), with Peer fee wallet 0x0000000000000000000000000000000000000002.
Use local admin payout to set the Peer fee and local admin payout-settings to set its wallet.
--fee-bps is the merchant fee,
0–1000 (default 0). A nonzero merchant fee requires an enabled Master Merchant Account; otherwise
the save returns 403 PAYOUT_MERCHANT_FEE_REQUIRES_MASTER_ACCOUNT without changing settings.
New payouts use zero merchant fees whenever that permission is off; existing payouts keep their fee snapshots.
Peer fees are separate and only admins can set them (nonzero requires Concierge).
--payout-step is the whole USDC every payout must be a multiple of,
PAYOUT_MIN_USDC–1000 (default 100). --support-url is optional: an https:// link or an email,
stored as mailto:. Each run saves every setting, so leaving it out saves no support link, and
the customer checkout then shows no contact line. A bare --support-url is a parser usage error;
an empty value (--support-url "") stops with "--support-url is required". A rail Peer has switched off (see local admin payout-settings)
can still be saved; new payouts leave it out. The response is the saved settings plus
availableRails and availableFundingTokens, what Peer currently allows. Each payout keeps
the step and rails it was created with; the customer support link is read from current merchant settings.
Example:
peer-pay-cli payouts configure-local --local --rails venmo,cashapp --fee-bps 0 --payout-step 50 --support-url help@example.com
payouts simulate-funding
Add a simulated Relay record to a local payout's deposit address and check its funding.
peer-pay-cli payouts simulate-funding PAYOUT_ID [options]
Options: --status, --received, --sent, --relay-request-id, --superseded-by, --refund-tx-hash.
Local profiles only; hosted profiles reject it before any request. Each call adds one record, as
if Relay had reported one payment to the deposit address, then runs the same funding check the API
runs. --status is the Relay status: success, failure, refund, or pending. --received
is the USDC that reached the customer's wallet on Base for that record; it is required with
success and not allowed with any other status. --sent is the funding-token amount the merchant
sent (default 0), and is not allowed with failure or refund. The local simulator has no Relay
or Base reads, so these amounts stand in for them.
The payout moves exactly as a hosted one would. It becomes FUNDED and sends
PAYOUT_ORDER_FUNDED when the USDC received covers the payout, or when the merchant sent at least
the quoted funding amount, no record is pending and some USDC arrived. If USDC arrived below
the payout and you sent less than funding.amount, the payout stays AWAITING_FUNDING with
fundingStatus: PARTIALLY_FUNDED and sends
PAYOUT_ORDER_PARTIALLY_FUNDED on becoming partial or when received grows, before the quote
expires. If you sent the full funding.amount and part is still converting, the payout keeps
waiting. A failure or refund record counts as nothing received: the payout keeps waiting
until its deadline. Each on-time record that adds USDC moves funding.quoteExpiresAt to 24 hours after
it, unless it is already later. When the deadline passes, a partly funded payout with nothing
pending becomes FUNDED with a deposit set from what arrived and sends PAYOUT_ORDER_FUNDED.
funding.sentAmount and funding.missingAmount are in the funding token;
funding.expectedAmount and funding.receivedAmount are in USDC. Funds that arrive after a
payout left AWAITING_FUNDING only raise its received amount.
Money outside the normal funding path is recorded as a funding issue, as the API does, with a
FUNDING_ISSUE timeline entry: USDC received above the quote when the payout is funded and
--sent totals more than the quoted funding.amount (OVERPAYMENT), USDC that arrives after funding closed or on an expired payout (LATE_FUNDS), and
each refund record (ROUTE_REFUND, keyed on --relay-request-id, or a generated id when it is
omitted). Running the check again records nothing new. See payouts funding-issues.
--relay-request-id names the record's Relay request for any status; it is generated when omitted, and an ID the
payout already has returns 409 PAYOUT_FUNDING_RECORD_EXISTS. --superseded-by (only with --status pending)
links a quote-time record to the request Relay moved its deposit to, as Relay does for Bitcoin. While that request is
among the payout's records, the linked record is dropped and the request keeps the earlier time; a record can't
supersede itself. This is the same superseded-record rule the API applies to Relay's records. --refund-tx-hash (only
with --status refund) records the refund transaction on the route-refund issue: a Bitcoin txid for a BTC payout, a
0x hash otherwise; a hash in the other format is recorded as no hash, as the API records it.
A Bitcoin "phantom" record keeps the payout waiting until its fill arrives:
peer-pay-cli payouts simulate-funding PAYOUT_ID --local --status pending --relay-request-id q1 --superseded-by f1
peer-pay-cli payouts simulate-funding PAYOUT_ID --local --status success --relay-request-id f1 --received 102 --sent 0.00102
Example:
peer-pay-cli payouts simulate-funding PAYOUT_ID --local --status success --received 106.4 --sent 110
payouts sweep-local
Run the local payout jobs: funding, settlement, credentials, wallet balances.
peer-pay-cli payouts sweep-local [options]
Options: --now.
Local profiles only; hosted profiles reject it before any request. --now is the ISO time the
sweep runs at (default: the current time). Pass a time after a payout's funding.quoteExpiresAt
to reach the deadline without waiting: a payout with nothing received becomes EXPIRED and sends
PAYOUT_ORDER_EXPIRED, and a partly funded one becomes FUNDED with what arrived and sends
PAYOUT_ORDER_FUNDED. A pending record keeps it waiting until that record resolves.
The response reports the number checked and the resulting payout views in items.
The sweep then runs the jobs the API runs in the background:
- Settlement reconcile refreshes every
LISTINGandPAYINGpayout from its simulated deposit and escrow events, with the same decision as the API. A buyer who drops (their intent expires or is pruned) moves the payout back toLISTINGwith oneACTION_REQUIREDcustomer email per buyer. A buyer who pays part of what is left records a partial fill aspayouts simulate-paymentdescribes. A rest at or below 0.10 USDC is swept as dust and settles. A withdrawn deposit's exit waits while a withdraw or USDC transfer for the attempt is still in flight; a relist or Peer Pay group send does not hold it.settlementreports{ checked, moved }. - Credential check reads the simulated credential of every
LISTINGVenmo, Cash App or PayPal payout (Zelle, Revolut and Chime have nothing to connect). One that is notactivegets oneACTION_REQUIREDreconnect email per listing, unless it is still skipped. The job clears the skip when it sees an active connection; later lapses get the same reconnect email as any connected listing.credentialsreports{ checked, emailed, skipped }:checkedcounts all examined listings, including skipped ones. An active skipped listing is cleared and counted only as checked, with no email; an inactive skipped listing also counts underskipped, with no email. - Wallet check compares each wallet that has a
payouts simulate-wallet-balancerecord with itsFUNDEDandREADYpayouts funded at least 10 minutes before--now. When the balance no longer covers their unpaid amounts (the deposit less what buyers paid), it handles the newest first. An unpaid payout becomesCANCELLEDwithcancelSource: "PEER_APP", aPAYOUT_ORDER_CANCELLEDwebhook and a cancelled email. A partly paid payout becomesSETTLEDwithsettlement.returnedAmount,PAYOUT_ORDER_SETTLEDand the settled email. Both count incancelled. A payout whose listing or USDC transfer is being sent is not cancelled.walletBalancesreports{ wallets, cancelled }.walletscounts distinct wallets with aFUNDEDorREADYpayout past the grace period, including wallets with no simulated balance.
There is no local send dispatcher (the API's dispatch-payout-transactions job): a send Pay
queued from the customer wallet waits for payouts resolve-send.
Example:
peer-pay-cli payouts sweep-local --local --now 2026-09-26T12:00:01Z
payouts simulate-credential
Set the simulated Curator credential of a local payout's fiat account.
peer-pay-cli payouts simulate-credential PAYOUT_ID [options]
Options: --status.
Local profiles only; hosted profiles reject it before any request. The customer must have saved a
payout method first (PUT /api/v1/cashout-checkout/:id/payout-method), otherwise it returns 409
PAYOUT_METHOD_MISSING. A crypto, Zelle, Revolut or Chime payout method has nothing to connect, so
it returns 409 PAYOUT_METHOD_NO_CREDENTIAL. --status is active, inactive, or missing, and
applies to every local payout that pays the same rail and handle, like a Curator seller credential.
The next checkout state read moves an unlisted payout from FUNDED to READY when it is active,
and back when it is not, unless the customer skipped connecting. A state read or credential check that sees an
active connection clears the skip. While a payout is LISTING, inactive or missing adds
CONNECT_RAIL to the checkout actions, even if it is skipped. The next payouts sweep-local
sends one reconnect email for the listing unless it is still skipped. The response has the
payee hash and the status.
Pass the credential status explicitly: the interactive prompt offers funding statuses, which this command refuses. See Local customer checkout for the customer routes.
Example:
peer-pay-cli payouts simulate-credential PAYOUT_ID --local --status active
payouts resolve-send
Confirm or fail what Pay is sending from a local payout's customer wallet.
peer-pay-cli payouts resolve-send PAYOUT_ID [options]
Options: --result, --count.
Local profiles only. Checkout's confirm, change-method, relist and send-to-address
queue sends from the customer's wallet and return the
checkout state with sending set to LISTING, TRANSFER, WITHDRAW or RELISTING and no actions.
The hosted API sends them with the Peer Pay signer; locally nothing is sent and they wait for
this command, which stands in for the API's dispatch-payout-transactions job and Base and
returns the checkout state.
Only sends QUEUED or SENT when the call starts can land in it. One queued while it runs waits for the
next resolve-send, as a separate Base transaction would. So a Venmo or PayPal listing takes a
second resolve-send with the default count: the first lands the approve and deposit, the second the Peer Pay group.
--result failed on the group row queues a withdraw of the listing; the next resolve-send
lands it and the payout returns to FUNDED. While a buyer pays, a failed group send waits.
The first resolve-send after the buyer's payment lapses queues the withdraw, and the one
after that lands it.
Each call first re-applies waiting failed sends and confirmed group-exit withdraws held by a
buyer. This includes exits waiting at their retry cap (3 withdraws). When only such waiting
rows exist, the call answers 200 with the checkout state, lands nothing, and ignores
--result failed.
--result confirmed(default) lands every still-pending send queued when the call started, in order. Any fiat (Venmo, Cash App, PayPal, Zelle, Revolut or Chime) confirm queues the USDC approve thencreateDeposit, and the payout becomesLISTINGwith its deposit linked. A direct Base USDC confirm queues one transfer and the payout becomesSETTLED, withPAYOUT_ORDER_SETTLEDand the settled email. An unpaid method change's withdraw returns the payout toFUNDEDwith the method cleared. If a buyer payment opened after a method-change withdraw was queued, the withdraw returns only what is free, the deposit stays listed, and the payout settles if the buyer pays.- A bridged TRANSFER to a Relay or NEAR Intents deposit address becomes BRIDGING when confirmed, keeping
the attempt ACTIVE and the payout READY (crypto) or LISTING (remainder). It emits no settled
webhook or email yet. Use
payouts simulate-payout-bridgeto provide delivery or refund evidence. A failed Base TRANSFER marks its bridge NOT_SENT and follows the existing failure rules. --result failedreverts the first send queued when the call started. A failed listing or crypto payout Base transfer closes the attempt and returns the payout toREADYso the customer can confirm again. A failed withdraw leaves the listing in place. The state haslastSendFailed: trueuntil the next send starts.
With confirmed, --count N lands only the first N sends queued when the call started, in order (default: all).
A send to an address queues a withdraw then a transfer, so --count 1 lands the withdraw alone
and the state shows sending: "TRANSFER".
A confirmed SET_INTENT_RANGE send sets the deposit's range to what is left, records a RELISTED
timeline row and lets buyers signal again. A failed send leaves the old range and the checkout
offers RELIST.
After a partial payment, a method change returns to FUNDED with the method cleared, keeping depositAmount and what was paid. The next confirm lists only the
unpaid amount, below PAYOUT_MIN_USDC if necessary. A direct Base USDC send to an address ends SETTLED with
settlement.sentAmount, settlement.sentTo and payoutTransfer. A bridged remainder stays
LISTING until delivery, then reports the destination payout. A verified refund returns READY
with the destination saved and refund losses deducted from the next payout. If its transfer fails after the withdraw lands,
it settles returned instead. If a buyer holds the deposit when the withdraw lands, the withdraw
returns only what is free, the queued transfer fails and the listing stays open. If the withdraw
returns another amount, the queued transfer fails and the payout settles returned.
A withdraw landing while a buyer holds the deposit returns only the free part and stops the deposit taking
new buyers, so a later partial payment is not relisted.
A held group-exit withdraw stays pending (sending: WITHDRAW). After the buyer's payment
lapses or partially completes, the first resolve-send queues another withdraw and the next lands it, returning to
FUNDED with lastSendFailed: true, keeping depositAmount and what buyers paid. A buyer
who pays in full settles the payout. A failed group-exit withdraw while LISTING or PAYING
queues one retry for the next call, up to 3 withdraws total on the attempt. lastSendFailed also stays true while a
payout whose group send failed is back in FUNDED or READY. If the deposit is gone,
settlement refresh closes the attempt using every recorded withdrawal.
While a send is in flight, confirm, change-method, relist, send-to-address and the
payout-method update return 409 CASHOUT_SEND_IN_PROGRESS with the payout's status and actions.
While a bridge is QUOTED or BRIDGING, those customer mutations and skip-connect return 409
CASHOUT_PAYOUT_BRIDGING (“Your cashout is on its way; wait for it to arrive”), with
{ status, actions }; preview returns 409 CASHOUT_PAYOUT_QUOTE_NOT_ALLOWED.
Confirm returns 409 CASHOUT_CONFIRM_CONFLICT if the payout already has an active attempt.
Relist returns 409 CASHOUT_RELIST_NOT_ALLOWED when it is not available. Send-to-address returns
409 CASHOUT_SEND_NOT_ALLOWED when it is not available, 409 CASHOUT_REMAINDER_CHANGED when
what is left changed, or 422 CASHOUT_ADDRESS_NOT_ALLOWED for a disallowed payout address.
With nothing queued and nothing waiting, this command returns 409 PAYOUT_SEND_MISSING.
Example:
peer-pay-cli payouts resolve-send PAYOUT_ID --local
peer-pay-cli payouts resolve-send PAYOUT_ID --local --result failed
For a NEAR payout, a QUEUED TRANSFER at or after sendBy fails SEND_BY_PASSED whichever
--result is given to resolve-send. No USDC moves: the bridge takes NOT_SENT, a whole
payout stays READY, and a send-the-rest exit settles returned. An already SENT row is not
failed by this deadline.
payouts simulate-payout-quote
Set the next local payout price, or make it refuse.
peer-pay-cli payouts simulate-payout-quote PAYOUT_ID [options]
Local profiles only; hosted profiles reject the command before any request.
| Option | Meaning |
|---|---|
--local | Select the local simulator profile |
--amount DECIMAL | Decimal destination-token output for the next Relay or NEAR preview or binding quote; zero is allowed as a fixture and makes that quote fail as too small. Excess destination-token decimals make the consuming quote return 400 CASHOUT_PAYOUT_BRIDGE_SIMULATION_INVALID; amounts are never rounded |
--refuse | AMOUNT_TOO_LOW, ROUTE_UNAVAILABLE or FAILED; mutually exclusive with --amount |
Supply exactly one of --amount or --refuse. The next Relay or NEAR preview or binding quote for
this payout consumes the override once; direct Base USDC does not consume it.
AMOUNT_TOO_LOW (or a zero output) returns 422 CASHOUT_PAYOUT_AMOUNT_TOO_LOW;
ROUTE_UNAVAILABLE returns 422 CASHOUT_PAYOUT_ROUTE_UNAVAILABLE for Relay or NEAR;
FAILED returns 502 CASHOUT_PAYOUT_QUOTE_FAILED, with the API’s exact messages.
Use ROUTE_UNAVAILABLE to simulate unavailable ZEC payouts, including a hosted API without
a partner JWT. The next preview, confirm or send-to-address consumes it once, returns 422
and writes no attempt, bridge or send. Use FAILED to simulate a NEAR request failure.
The local-only route is POST /local/payouts/:id/payout-quote.
peer-pay-cli payouts simulate-payout-quote PAYOUT_ID --local --refuse ROUTE_UNAVAILABLE
payouts simulate-payout-bridge
Deliver, refund or hold a local bridged payout; NEAR bridges use NEAR's statuses.
peer-pay-cli payouts simulate-payout-bridge PAYOUT_ID [options]
Local profiles only; hosted profiles reject the command before any request. A payout bridge
must already be BRIDGING after payouts resolve-send. Otherwise this answers 409
PAYOUT_BRIDGE_NOT_SENDING, “Nothing is on its way to another network”.
The local-only route is POST /local/payouts/:id/payout-bridge.
| Option | Meaning |
|---|---|
--local | Select the local simulator profile. |
--status | Required: success, refund, failure or pending. |
--delivered DECIMAL | Required with success; positive destination-token amount. More decimals than the token supports returns 400 PAYOUT_BRIDGE_SIMULATION_INVALID, including trailing zeros. Delivery settles the payout. |
--refunded DECIMAL | Positive USDC amount no greater than sent. Refund defaults to the full send; failure without this flag keeps the bridge BRIDGING. |
A refund, or failure with --refunded, returns the payout to READY; any difference between sent and refunded
USDC is a refund fee deducted from the next payout. The attempt ends (FAILED for a whole crypto
payout, CLOSED for a send-the-rest exit), and a send-the-rest exit restores the destination as the
saved payout method. It records a PAYOUT_REFUNDED timeline entry and one ACTION_REQUIRED
customer email. The fee appears as settlement.refundFeeAmount when the payout settles.
Pending leaves it BRIDGING.
Success requires a delivered amount greater than zero, in destination tokens.
--delivered is only valid with success; --refunded is only valid with refund or failure.
Invalid status/amount combinations return 400 PAYOUT_BRIDGE_SIMULATION_INVALID. --refunded
uses USDC decimals (at most six), must be greater than zero and no greater than the sent USDC.
With refund, omitting it refunds the whole send; with failure, omitting it means no refund
and leaves BRIDGING. sweep-local never advances bridges: only simulated outcomes do.
For a NEAR bridge, success evidence passes the hosted NEAR status checks. --delivered must be
positive and at least the stored quote’s minAmountOut converted to ZEC. Zero or a delivery below that
minimum returns 400 PAYOUT_BRIDGE_SIMULATION_INVALID, with the minimum in the message.
The bridge remains BRIDGING: no state is written and no webhook is emitted.
For a NEAR bridge, --status maps through NEAR’s statuses:
| CLI status | NEAR status |
|---|---|
success | SUCCESS |
refund | REFUNDED |
failure | FAILED |
pending | PROCESSING |
The Zcash delivery hash is 64 hex digits without 0x. A failure without --refunded
stays BRIDGING, just as it does for Relay. These commands simulate evidence locally and
never call Relay, NEAR or send funds.
peer-pay-cli payouts simulate-payout-bridge PAYOUT_ID --local --status success --delivered 0.033
payouts simulate-payment
Simulate a buyer payment step on a listed local payout and refresh its settlement.
peer-pay-cli payouts simulate-payment PAYOUT_ID [options]
Options: --step, --amount.
Local profiles only. This stands in for a buyer's intent on the escrow deposit, then runs the
settlement refresh an indexer update triggers. --amount pays part of what is left (default: all
of it). --step is one of:
signaled: a buyer opens a payment for everything left. The payout movesLISTING→PAYINGand sendsPAYOUT_ORDER_MATCHED; change-method returns 409CASHOUT_BUYER_PAYMENT_ACTIVE. A second open payment returns 409PAYOUT_BUYER_PAYMENT_ACTIVE.replaced: the open payment expires and a new buyer signals before settlement refreshes. The payout staysPAYING, records the new intent and sends onePAYOUT_ORDER_MATCHEDper buyer, without a buyer-dropped email.expired: the open payment expires and the payout returns toLISTING. Its locked funds remain reclaimable: send-to-address withdraws them, and the next signal prunes the expired intent before locking those funds for the new buyer.pruned: the open payment is released. The payout returns toLISTING.fulfilled: the buyer completes the payment for--amount(default: everything left). Paying everything left moves the payout toSETTLED, setssettledAt, sendsPAYOUT_ORDER_SETTLEDand records the settled customer email. A smaller amount records a partial fill, sendsPAYOUT_ORDER_PARTIALLY_PAID, records the partially-paid customer email and movesPAYING→LISTING. If the deposit still exists and accepts buyers, and no send for the attempt is in flight, it also queues a relist forpayouts resolve-send. The email'srestRelistedis true only when this relist is queued now, or a relist is already in flight on a deposit that accepts buyers. Otherwise it is false. A rest at or below 0.10 USDC is swept as dust and the payout settles withsettlement.dustAmount; the settled email is the only email for that payment.
Local non-USD payout estimates and new buyer intents use local settings' existing rates
fixture, in currency units per USD: EUR 0.92, GBP 0.79, AUD 1.52, CAD 1.37,
CHF 0.88, CNY 7.25, MXN 18.5, NZD 1.68, SGD 1.35, TRY 34, ZAR 18.2
by default. The bound 18-decimal rate
is that fixture value × 10^18; USD is always fixed at 1.00 and ignores the fixture. There is
no local staleness logic and no live oracle call. A missing fixture rate leaves the currency
available without a display estimate; the state GET still succeeds. Binding a new signal or
replacement requires the rate and returns 400 if it is missing. For example:
peer-pay-cli local settings --data '{"rates":{"EUR":"0.92","GBP":"0.79"}}'
signaled and replaced bind the current local rate; fulfilled keeps that rate even if
rates changes afterward. A 40 USDC EUR payment at the default rate reports
{ "currency": "EUR", "amount": "36.80" }. Checkout buyerPayment, partialPayment.paidFiat,
merchant partialFills[].fiat, settlement.buyerPaidFiat and webhook partialPayment.fill.fiat
mirror the hosted fields. All payment controls, paid amounts, fees and accounting remain USDC.
Older local state files load with USD for fiat methods and escrow attempts, fixed USD fill
rates, matching the hosted migration. All catalog currencies are available by default.
A zero amount returns 400 PAYOUT_PAYMENT_AMOUNT_INVALID; an amount above what is left returns
400 PAYOUT_PAYMENT_ABOVE_REMAINING. A new signal or replacement before the relist lands returns
409 PAYOUT_LISTING_NOT_RELISTED. Once a withdraw landed while a buyer held the deposit,
signaled and replaced return 409 PAYOUT_LISTING_NOT_ACCEPTING: the escrow takes no new buyer.
replaced, expired, pruned and fulfilled return 409 PAYOUT_BUYER_PAYMENT_MISSING without an open
payment. Every step returns 409 PAYOUT_ATTEMPT_NOT_LISTED when the payout is not listed. The
response is the customer's checkout state. The open-payment checks precede the accepting-buyers check.
Example:
peer-pay-cli payouts simulate-payment PAYOUT_ID --local --step signaled
peer-pay-cli payouts simulate-payment PAYOUT_ID --local --step fulfilled
payouts simulate-peer-withdraw
Simulate the customer withdrawing a listed local payout in the Peer app, outside Pay.
peer-pay-cli payouts simulate-peer-withdraw PAYOUT_ID
Local profiles only. This stands in for a withdraw the customer makes in the Peer app, then runs the
settlement refresh. An unpaid payout ends CANCELLED with cancelSource: "PEER_APP", sends
PAYOUT_ORDER_CANCELLED and records the cancelled email; depositAmount becomes what the
withdraw returned. After a partial payment it ends SETTLED with settlement.returnedAmount,
keeping depositAmount and what buyers paid, and sends the settled webhook and email. If the customer had asked Pay to change method, the payout returns to FUNDED
instead. While a buyer payment holds the deposit, the withdraw returns nothing, the deposit stays
listed but takes no new buyer, and the payout keeps its status; the buyer's payment still completes.
Returns 409 PAYOUT_ATTEMPT_NOT_LISTED when the payout is not listed.
The exit waits only for a withdraw or USDC transfer from the wallet that is still in flight, as in the API; a queued relist does not hold it.
Example:
peer-pay-cli payouts simulate-peer-withdraw PAYOUT_ID --local
payouts simulate-wallet-balance
Set the simulated USDC balance of a local payout's customer wallet for the wallet check.
peer-pay-cli payouts simulate-wallet-balance PAYOUT_ID [options]
Options: --amount.
Local profiles only. --amount is the wallet's USDC on Base, such as 50. It applies to every
local payout of the same customer. payouts sweep-local then runs the wallet check. A wallet with
no simulated balance covers all its payouts. The local checkout state uses this balance too,
with the same 10-minute funding grace and newest-first rule: a short payout shows
walletShortfall, offers no actions, and returns 409 CASHOUT_WALLET_SHORT on confirm; the
wallet check in payouts sweep-local closes it.
Example:
peer-pay-cli payouts simulate-wallet-balance PAYOUT_ID --local --amount 50
payouts emails
List a local payout's customer emails with their resume links.
peer-pay-cli payouts emails PAYOUT_ID
Local profiles only. The local simulator records email metadata and resume links; it neither renders
email bodies nor sends email. The simulator records every customer email the API would
send: FUNDED, ACTION_REQUIRED, PARTIALLY_PAID, CANCELLED and SETTLED. Each entry has
restRelisted: a boolean for PARTIALLY_PAID, null otherwise. A state file with a
PARTIALLY_PAID email missing this flag is refused on load; older emails of other kinds load
with restRelisted: null. ACTION_REQUIRED covers a dropped buyer, a reconnect, and a refunded
crypto payout. The refunded payout's email adds no timeline row of its own; the refund has a
PAYOUT_REFUNDED row.
Each email has a resume link on the local server whose t token
opens the customer checkout API like the merchant's checkout link. Resume tokens derive from each
email id with a separate fixed local secret; the local server re-derives them on every request.
Example:
peer-pay-cli payouts emails PAYOUT_ID --local
payouts funding-issues
List local payout funding issues.
peer-pay-cli payouts funding-issues [options]
Options: --status.
The output retains the admin field cashoutId. Local profiles only: the hosted route, GET /api/v1/admin/cashouts/support, needs the admin key,
which the CLI never holds. Returns { fundingIssues } as the admin Payouts page shows them,
newest first by detectedAt, at most 200.
--status open (the default) lists unresolved issues; --status all includes resolved ones. Each
issue has its kind (LATE_FUNDS, OVERPAYMENT or ROUTE_REFUND), amountUsdc (null for a
route refund, which Relay pays back in the funding token), the payout, and how it was resolved.
Example:
peer-pay-cli payouts funding-issues --local --status all
payouts resolve-funding-issue
Record how Peer returned or claimed a local funding issue's money.
peer-pay-cli payouts resolve-funding-issue FUNDING_ISSUE_ID [options]
Options: --data.
Local profiles only, for the same reason as payouts funding-issues. It records the outcome and
moves no money. The body is one of:
{ "resolution": "RETURNED", "txHash": "0x…64 hex", "note": "Sent back to the merchant's refund address" }
{ "resolution": "CLAIMED", "note": "Merchant asked Peer to keep it for the next payout" }
note is 1–500 characters and a return needs the transaction hash: a 0x… hash, or a Bitcoin txid
(64 hex digits, no prefix). Bitcoin txids are stored lower-case; 0x hashes keep their supplied case. The CLI adds
"actor": "peer-pay-cli" when the body names no actor. An issue is resolved once: a second resolve
returns 409 CASHOUT_FUNDING_ISSUE_RESOLVED, and an unknown ID returns 404
CASHOUT_FUNDING_ISSUE_NOT_FOUND. Each resolve is written to the admin change history.
Example:
peer-pay-cli payouts resolve-funding-issue FUNDING_ISSUE_ID --local --data @resolve.json
payouts timeline
Show a local payout's dashboard timeline and next step.
peer-pay-cli payouts timeline PAYOUT_ID
Local profiles only. Returns { timeline, nextStep } as the dashboard's payout detail shows them:
one entry per moment, oldest first, with its type, text, txHash, txChainId and occurredAt. txChainId identifies
the transaction’s network (null when no link is available). A bridged payout's SETTLED row links
to the destination network; TRANSFER_SENT and PAYOUT_REFUNDED link to Base. The moments are:
- Funding and settlement:
CREATED,PARTIALLY_FUNDED,FUNDED,FUNDING_EXPIRED,MATCHED,PARTIALLY_PAID,SETTLED,CANCELLED, andFUNDING_ISSUE. - Customer steps:
SIGNED_IN,METHOD_READY,LISTED,RELISTED,LISTING_WITHDRAWN,TRANSFER_SENT, andPAYOUT_REFUNDED. - Action-required emails:
BUYER_DROPPEDandRECONNECT_NEEDED; reconnect covers Venmo, Cash App and PayPal.
nextStep says what happens next, such as
"Waiting for a buyer" or Sending {SYMBOL} on {Network} to the customer while bridging,
and is null once the payout is final.
Example:
peer-pay-cli payouts timeline PAYOUT_ID --local
Local customer checkout
The simulator serves the API's customer router under /api/v1/cashout-checkout.
Every route needs x-cashout-token: TOKEN, using the t query parameter from checkoutUrl
or an email resume link. Except for the summary, also send
Authorization: Bearer local-player:<email> with the payout's customer email.
| Method | Path (under /api/v1/cashout-checkout) | Authentication |
|---|---|---|
| GET | /:id | Link token only. |
| GET | /:id/state | Link token and customer login. |
| PUT | /:id/payout-method | Link token and customer login. |
| POST | /:id/payout-quote | Link token and customer login. |
| POST | /:id/confirm | Link token and customer login. |
| POST | /:id/skip-connect | Link token and customer login. |
| POST | /:id/relist | Link token and customer login. |
| POST | /:id/send-to-address | Link token and customer login. |
| POST | /:id/change-method | Link token and customer login. |
| GET | /:id/activity | Link token and customer login. |
A missing login returns 401 CASHOUT_LOGIN_REQUIRED; an invalid local login returns
401 CASHOUT_LOGIN_INVALID, and another email returns 403 CASHOUT_PLAYER_MISMATCH.
An unknown payout id returns the same 401 CASHOUT_TOKEN_INVALID as a wrong link token.
The customer routes also return 422 CASHOUT_RAIL_UNAVAILABLE or CASHOUT_CREDENTIAL_INACTIVE,
and 409 CASHOUT_METHOD_LOCKED or CASHOUT_NOT_READY when the action
is unavailable. Relisting below the method minimum returns 409 CASHOUT_RELIST_NOT_ALLOWED
(“What’s left is below the payout method minimum”). See the API's customer errors
for the full list.
The smallest payout step and the smallest single buyer payment are PAYOUT_MIN_USDC, a whole USDC
amount from 1 to 10 read from the environment when serve starts, 10 when unset. This variable
configures the local simulator only; the minimum payout is 10 USDC in production. The retired
CASHOUT_MIN_USDC variable is ignored by the CLI; use PAYOUT_MIN_USDC instead. The payout settings responses
return it as payoutStepRange, and a listing below it returns 422 CASHOUT_AMOUNT_OUTSIDE_RAIL_LIMITS.
The local POST /api/v1/cashout-checkout/:id/skip-connect route takes no body and requires
both the link token and customer login. When a funded Venmo, Cash App or PayPal method needs connecting, its actions
include CONNECT_RAIL and SKIP_CONNECT. Skipping returns 200 with the fresh READY state
and method.connectSkipped: true; it records METHOD_READY and lets confirm list without an
active credential. Buyers prove their payment, as on Zelle. State reads keep it READY while
the connection is inactive. A skipped listing still offers CONNECT_RAIL, so the customer can
connect while it waits. Connecting helps find a buyer faster: in Peer Pay, the same live
listing becomes available to merchants that take only connected accounts as soon as Peer
sees the connection. Nothing is relisted, and Peer Pay sends nothing. The simulator mirrors clearing the skip when a
state read or credential check sees an active connection, with no status change or new send or timeline event.
It then treats the listing like any connected one, including a reconnect email if the
connection later lapses. A skipped listing gets no reconnect email until Peer Pay sees it connect
(payouts sweep-local counts an inactive skipped listing under skipped). Every payout-method save and the method restore after a refunded send-to-address exit clears the skip. Withdrawing a listing to change
its method returns the payout to FUNDED with no method and connectSkippedAt: null, so a
new Venmo, Cash App or PayPal method must connect or skip again. State files saved before connectSkippedAt existed
load that field as null. When the action is unavailable, this route returns 409 CASHOUT_SKIP_NOT_ALLOWED
(“This cashout can’t skip connecting now”) with { status, actions }. A QUOTED or BRIDGING payout
returns 409 CASHOUT_PAYOUT_BRIDGING first. Final payout links reject POSTs with 401 CASHOUT_LINK_EXPIRED.
The local payout-method route applies the backend's account rules. The body is
{ "rail": "venmo" | "cashapp", "payeeHandle" } or
{ "rail": "zelle" | "chime", "payeeHandle" }. Multi-currency rails require:
{ "rail": "paypal", "payeeHandle": "alice", "paypalEmail": "alice@example.com", "currency": "EUR" }
{ "rail": "revolut", "payeeHandle": "alice", "currency": "CHF" }
PayPal and Revolut accept their payout catalog currencies.
Missing currency or a currency outside the rail's catalog is a 400; other rails reject the
extra field. Old PayPal/Revolut tabs must reload. An oracle estimate never gates
saving or confirming. Changing a listed method or
currency withdraws it and returns to FUNDED, preserving paid parts in their original currencies.
For crypto the strict body is { "rail": "relay_8453", "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "address": "0x1234567890123456789012345678901234567890" }.
The rail selects the network; another token/network uses its own catalog token and rail.
The same { rail, tokenAddress, address } body serves payout-quote and send-to-address.
Send-to-address is offered after a partial payment only while the rest is listed, no buyer is paying
and the payout includes a crypto network; without one it returns 409 CASHOUT_SEND_NOT_ALLOWED.
Local checkout state uses the API’s settleTx: it is null when the newest attempt’s newest
bridge was refunded, even if the payout later settles with the remainder returned.
The backend’s destination and current-rails checks apply, including
422 CASHOUT_PAYOUT_NETWORK_UNAVAILABLE for a network missing from the payout's stored rails.
Hosted, ops can remove a network from open payouts; locally, create the payout with the rails you want.
POST /api/v1/cashout-checkout/:id/payout-quote is available in READY with crypto CONFIRM,
or LISTING with SEND_TO_ADDRESS. It returns destination, usdcAmount, estimatedAmount
and quotedAt. Local Relay estimates assume a fake 1 USD per token, rescaling USDC’s six
decimals to the destination token; it never calls Relay. Base USDC needs no Relay quote.
A Venmo username is 5–30 letters, digits, - or _ after one leading @. A Cash App cashtag is
lowercased after one leading $, and must be 1–20 letters or digits. A PayPal username is
lowercased after a paypal.me/ link or one leading @, and may use letters, digits, ., _ and
-. A Zelle email is trimmed and lowercased, with one @, no whitespace, a dotted domain and
up to 254 characters. A Revtag drops one leading @, a leading revolut.me/ link with or without
https:// or http:// and any trailing /, is lowercased, and may use only letters, digits, ., _ and -.
A $ChimeSign is lowercased with exactly one leading $ (added when missing) and no whitespace
or further $. Anything else returns 422 CASHOUT_PAYEE_HANDLE_INVALID. An invalid PayPal email
returns 422 CASHOUT_PAYPAL_EMAIL_INVALID. The saved PayPal email appears only in the customer's
checkout state, as method.paypalEmail. A Zelle, Revolut or Chime method is READY at once, with
a METHOD_READY timeline entry and no CONNECT_RAIL.
The local Curator stand-in knows every valid handle, so four Curator refusals never happen
locally: CASHOUT_PAYEE_HANDLE_INVALID (a Zelle, Revolut or Chime handle Curator refuses; the
local handle rules above still apply), CASHOUT_PAYEE_NOT_FOUND (no such account),
CASHOUT_PAYEE_NOT_REGISTERED (a PayPal account not set up in Peer) and
CASHOUT_PAYPAL_EMAIL_MISMATCH (an email that isn't the PayPal account's).
Payments
Create requires an order ID and either --rail or --data.
--currency sets fiatCurrency for fiat rails (for example, revolut + EUR)
and a crypto rail’s currency (for example, relay_8453 + USDC).
When omitted, fiat defaults to USD and crypto defaults to USDC; crypto rails ignore
fiatCurrency, and fiat rails ignore currency in a --data body. Common rails include venmo,
revolut, and relay_8453; see Payment platforms.
Use --currency, --refund-to, and --onramp-provider for crypto-specific
fields, or pass the canonical create-payment body with exclusive --data.
List returns merchant payments; cancel takes both the order ID and payment ID.
payments create
Use --payment-method-id for paymentMethodId, --quote-limit (1 to 20) for quoteLimit, and repeat --excluded-pay-to VALUE to build excludedPayToValues. --currency still maps to fiatCurrency for fiat rails and a crypto rail’s currency.
Create a payment attempt. Local sandbox permits multiple CREATED attempts; replacementPaymentId leaves prior attempts unchanged.
Hosted crypto route creation can return HTTP 502 with error code
CRYPTO_PAYMENT_ROUTE_FAILED. The CLI preserves that code and the provider's
diagnostic message. This is a generic route failure, not a minimum-amount or
fee-specific error. The local simulator does not call the route provider.
peer-pay-cli payments create ORDER_ID [options]
Options: --rail, --currency, --refund-to, --onramp-provider, --replacement-payment-id, --payment-method-id, --quote-limit, --excluded-pay-to, --amount, --amount-currency, --expected-amount-version, --data.
On an open-amount order, --amount with --amount-currency (the fiat currency it is typed in) and
--expected-amount-version (the amountVersion you read) saves a new amount with
the payment and sends ORDER_AMOUNT_SET; see Orders for the rules and
error codes. Without them, the payment uses the saved amount. On a fiat rail, also
pass --currency with the same code as --amount-currency. Without --currency
the payment is in USD, so a non-USD amount is refused before the request is sent:
peer-pay-cli payments create ORDER_ID --rail revolut --currency EUR \
--amount 92 --amount-currency EUR --expected-amount-version 0
A fiat payment is also checked against the local settings maxOrderAmountUsdc
(default 10,000 USDC), like the hosted API, on the amount it would sign on chain:
the order amount plus any buyer-paid fees (PAYEE, or the buyer's share under
SPLIT). Above it, local creation returns HTTP 400 INTENT_ABOVE_MAX with
This payment would be ${intent} USDC with fees, above the ${max} USDC limit per payment
(the intent rounded up and the maximum rounded down to the cent). The check runs
before anything is stored: no payment, no PAYMENT_CREATED, and an open-amount
--amount is not saved. Exactly the maximum is allowed. For example, a PAYEE
10,000 USDC order with a 150 bps fee signs 10,152.284265 USDC and is refused on
venmo. Crypto, Zcash and Apple Pay payments sign no escrow intent and are not
checked.
Payment creation attaches the order token saved in the current profile when
available. --onramp-provider and --replacement-payment-id (or their canonical
fields in --data) require that token; create the order with orders create to
save it. Ordinary payment creation works without a saved token. Local privileged
creation returns HTTP 401 with Missing order token or Invalid order token,
matching the hosted API.
An order created before the merchant account changed owners takes no new payments. Hosted and
local creation return HTTP 409 ORDER_FROM_PREVIOUS_OWNER with This order was created before the merchant account changed owners. Ask the merchant for a new payment link. Payments already
on the order still settle to the previous owner, and payments recreate still recovers them.
Example:
peer-pay-cli payments create ORDER_ID --rail venmo --currency USD
payments list
List merchant payments with --status, --chargeback-status, --page,
and --limit:
peer-pay-cli payments list --status SETTLED --chargeback-status CHARGEBACKED --limit 10 --page 1
status accepts CREATED, SETTLED, FAILED, EXPIRED, or CANCELLED.
chargebackStatus=NONE selects payments without a chargeback fact;
CHARGEBACKED selects payments with one. Filters combine before totals and
pagination; results are newest first. Page defaults to 1; limit defaults to 20
and has a maximum of 100. The payment-list schema has no orderId or search
filter; retrieve an order's payments through orders get ORDER_ID.
peer-pay-cli payments list
Options: --status, --chargeback-status, --page, --limit.
Example:
peer-pay-cli payments list
payments cancel
Cancel an open payment using its cached order token.
peer-pay-cli payments cancel ORDER_ID PAYMENT_ID
Example:
peer-pay-cli payments cancel ORDER_ID PAYMENT_ID
Webhooks
Create requires --url; --events defaults to all webhook events.
The signing secret is returned only on creation and saved in the selected profile.
Update accepts --url, --events, --active, and custom-header flags.
Omitting --events on update preserves existing subscriptions. Create and update
accept flags only; --data is not supported.
Hosted destinations require public HTTPS. Local destinations can use localhost
HTTP. test is a connectivity check with null business entities; use
test-order or simulate for business payloads. Deliveries shows attempt
history, status, and response details. Local failures retry after 60 seconds,
5 minutes, 30 minutes, 2 hours, 8 hours, and 24 hours.
Every event uses the {id, type, timestamp, data} envelope. The JSON id
equals the signed X-Webhook-Id and stays the same across delivery retries.
data includes order, payment, refund, and paymentBridge, with explicit
null values for absent entities. IDs, ISO timestamps, decimal amounts, and
chain IDs are strings.
Bridge events include the attempt's status, attempt number, amounts, transaction
fields, and timestamps; transaction arrays can be empty before broadcast.
Refund event statuses are PENDING or COMPLETED, distinct from the merchant
refund execution entity. See events and payloads for
refund fields, resize amounts, and chargeback snapshots and triggers.
webhooks list
List webhook destinations and subscriptions.
peer-pay-cli webhooks list
Example:
peer-pay-cli webhooks list
Hosted profiles require HTTPS webhook URLs for both webhooks create and
webhooks update. Local profiles may
use HTTP for localhost handlers. Updates that omit the URL keep it unchanged.
webhooks create
Register a webhook, show its signing secret in full once, and save it in the profile.
No --show-secrets flag is needed. The API never returns this secret from
webhooks list; export the saved value later with
peer-pay-cli credentials export --output FILE --webhook-id <id>.
peer-pay-cli webhooks create [options]
Options: --url, --events, repeatable --header NAME=VALUE.
With --url, omitting --events subscribes to every webhook event; pass comma-separated event names to select a subset.
Example:
peer-pay-cli webhooks create --local --url http://localhost:3000/webhooks/peer-pay --header X-Auth=secret
Repeat --header to add custom headers, for example --header X-Auth=secret --header X-Merchant=shop. Each entry splits at the first =: the name is trimmed
and lowercased, while the value is kept verbatim (including additional = and
spaces). Empty values are allowed; a missing = or empty name is rejected.
The backend WebhookHeadersSchema allows at most 10 headers, with no duplicate
names after normalization. Names may contain letters, digits, and these HTTP
header token characters: !#$%&'*+-.^_`|~. Values cannot contain CR, LF, or NUL;
the schema imposes no other value length limit. Forbidden names are connection,
content-length, content-type, expect, forwarded, host,
proxy-authenticate, proxy-authorization, te, trailer, transfer-encoding,
and upgrade; all proxy- and x-forwarded- prefixes are forbidden too.
x-webhook-id, x-webhook-timestamp, and x-webhook-signature are reserved.
Webhook create and update accept flags only; --data is not supported.
webhooks update
Update a webhook URL, events, headers, or active state.
peer-pay-cli webhooks update WEBHOOK_ID [options]
Options: --url, --events, --active true|false, repeatable --header NAME=VALUE, --clear-headers.
Only provided flags appear in the PATCH body. Omitted fields stay unchanged.
--events takes comma-separated event names and must contain at least one event;
--active false disables deliveries and --active true enables them.
--header replaces the entire custom header collection, matching the backend
PATCH; it does not merge with existing headers. Use --clear-headers to send
customHeaders: [] and remove all headers.
--header and --clear-headers cannot be combined. The same header rules above
apply to updates.
Example:
peer-pay-cli webhooks update WEBHOOK_ID --active false --header X-Auth=secret
webhooks delete
Delete a webhook and its locally saved secret (HTTP 204 with no body).
peer-pay-cli webhooks delete WEBHOOK_ID
Example:
peer-pay-cli webhooks delete WEBHOOK_ID
webhooks test
Send a connectivity test with empty business entities.
peer-pay-cli webhooks test WEBHOOK_ID
Example:
peer-pay-cli webhooks test WEBHOOK_ID
webhooks deliveries
Inspect the latest 50 webhook deliveries; pagination options are not supported.
peer-pay-cli webhooks deliveries WEBHOOK_ID
Example:
peer-pay-cli webhooks deliveries WEBHOOK_ID
Merchant
Hosted updates use the signed-in session and the backend's owner/manager checks.
update requires a JSON body with name; it can also set industryType,
industryOtherText, and integrationPath. settings applies a partial
merchant-config update, including payment rails, currency, fee payer, settlement,
checkout theme, and dashboard preferences. Unspecified config fields are retained;
a supplied merchantCheckoutTheme replaces the whole theme object.
Local config PUT validates the replacement and PATCH validates the merged result
before saving any fields. Invalid payouts return HTTP 400 without an errorCode,
with Unsupported payout destination chain, Unsupported payout destination token for chain,
or Non-Base payout destinations require sweep support. For example, Ethereum
USDC requires sweepEnabled: true; disabling sweep on that existing configuration
is rejected without changing the saved settings. Base USDC permits sweep to be disabled.
logo requires --file; supported formats are PNG, JPEG, WebP, and GIF,
up to 2 MB. tier requires --tier BASE or --tier PRO. Pricing and
availability are enforced by the backend. Key rotation invalidates the previous
key and saves the replacement; update any applications using the old key.
provision-wallets retries missing owner wallet setup without replacing existing
addresses. Concierge and Shopify commands submit the corresponding setup requests;
they do not complete those workflows locally.
merchant get
Use --management for the dashboard role-masked GET /api/v1/merchants/me view.
Read your merchant profile and settings.
peer-pay-cli merchant get
Options: --management.
Example:
peer-pay-cli merchant get --profile live
merchant update
Flags map to name, industryType, industryOtherText, and integrationPath. If --name is omitted, the CLI fetches GET /api/v1/merchants/dashboard/me and preserves its name before PUT. Empty industry strings clear those fields. Raw --data must include name and does not trigger the fetch.
Update business name, industry, or integration path.
peer-pay-cli merchant update [options]
Options: --name, --industry-type, --industry-other-text, --integration-path, --data.
Example:
peer-pay-cli merchant update --profile live --name "Example Store" --industry-type ecommerce
merchant settings
Local updates validate merged payout settings before saving; non-Base-USDC
payouts require sweep support. Both PATCH and PUT validate payout settings before
Apple Pay availability. PATCH filters EXCLUSIVE_SAR rails before payout validation;
PUT filters them afterward. Successful merchant PATCH/PUT and admin config PATCH
responses use the message Merchant config updated.
PATCH merchant config with first-class flags or an exclusive --data JSON body.
Theme and dashboard flags first fetch GET /merchants/me and merge existing
nested fields, because each object is replaced as a whole by the backend.
Avoid concurrent edits to those objects between the read and write.
Use --replace for PUT: fetch current writable fields, overlay flags, and require
every required config field. --replace --data @settings.json instead validates
and sends that full object directly, without a merge. Raw bodies are never
combined with individual request flags.
The MANAGER role cannot change sweepEnabled, destinationChainId, or
destinationToken; the backend rejects changes to these values. --replace
resends the current destination and sweep values as part of the complete config,
which is allowed when those values are unchanged.
Options:
--fee-payer: MERCHANT, PAYEE, or SPLIT.--buyer-fee-share-bps: Buyer share of total fees from 0 to 10000 in steps of 1000 (5000 = 50%) in SPLIT mode. Required when enabling SPLIT; subsequent PATCH updates can retain the saved rate. A full SPLIT replacement body must includebuyerFeeShareBps.--enabled-rails: Comma-separated supported rails.--default-payment-currency: Default fiat currency; an empty string sends null.--clear-default-payment-currency: Send null for defaultPaymentCurrency.--destination-chain-id: Decimal integer chain ID.--destination-token: Settlement token.--sweep-enabled: true or false; MANAGER role cannot change sweep or destination values; --replace may resend unchanged values.--dynamic-orders-enabled: true or false.--theme-preset: Checkout preset: default, dark, light, or custom.--theme-page-background-color: Checkout page background color (#rgb or #rrggbb).--theme-panel-background-color: Checkout panel background color (#rgb or #rrggbb).--theme-panel-text-color: Checkout panel text color (#rgb or #rrggbb).--theme-panel-text-muted-color: Checkout panel text muted color (#rgb or #rrggbb).--theme-panel-accent-color: Checkout panel accent color (#rgb or #rrggbb).--theme-panel-border-color: Checkout panel border color (#rgb or #rrggbb).--theme-button-background-color: Checkout button background color (#rgb or #rrggbb).--theme-button-text-color: Checkout button text color (#rgb or #rrggbb).--theme-currency-selector-enabled: true or false; show the checkout currency selector.--theme-pay-with-crypto-enabled: true or false; show pay with crypto.--theme-default-max-fee-percentage: Numeric checkout default maximum fee percentage.--quick-amounts-enabled: true or false; enable dashboard quick amounts.--quick-amounts-count: Dashboard quick amount count: 4, 6, or 8.--quick-amounts: Comma-separated positive amounts (at most eight).--dashboard-theme-preset: Dashboard theme preset.--dashboard-theme-accent-hex: Dashboard custom accent color.--max-fee-mode: flat or tranches; requires the matching percentage or tranches option. Currency is USD.--max-fee-percentage: Flat maximum fee percentage, 0 to 100; requires --max-fee-mode flat.--max-fee-tranches: JSON array inline or @filename with min, max (number or null), and value (0 to 100); requires --max-fee-mode tranches.--clear-dashboard-ui: Send null for dashboardUiConfig; excludes dashboard flags.--clear-max-fee: Send null for maxFeeConfig; excludes max-fee flags.--replace: PUT the full writable config: fetch current settings, overlay flags, and validate all required fields. With --data, send that complete object directly.--data: JSON body inline or @filename.
When a dashboard subobject does not exist, provide all its required fields:
quick amounts need enabled, count, and amounts; theme needs preset and accent.
Use --data to explicitly set nullable individual theme colors or dashboard
theme values to null. Clear the whole dashboard or max-fee object with
--clear-dashboard-ui or --clear-max-fee. --default-payment-currency ""
and --clear-default-payment-currency both send null.
Examples:
peer-pay-cli merchant settings --profile live --data @settings.json
peer-pay-cli merchant settings --profile live --fee-payer PAYEE --enabled-rails venmo,revolut
peer-pay-cli merchant settings --profile live --fee-payer SPLIT --buyer-fee-share-bps 400
peer-pay-cli merchant settings --profile live --theme-preset dark --theme-pay-with-crypto-enabled true
peer-pay-cli merchant settings --profile live --quick-amounts-enabled true --quick-amounts-count 4 --quick-amounts 10,20,50,100
peer-pay-cli merchant settings --profile live --max-fee-mode flat --max-fee-percentage 2
peer-pay-cli merchant settings --profile live --max-fee-mode tranches --max-fee-tranches '[{"min":0,"max":null,"value":2}]'
peer-pay-cli merchant settings --profile live --replace --fee-payer MERCHANT
merchant logo
Upload a PNG, JPEG, WebP, or GIF logo, up to 2 MB.
peer-pay-cli merchant logo [options]
Options: --file.
Example:
peer-pay-cli merchant logo --profile live --file ./logo.png
merchant tier
Select the BASE or PRO pricing plan.
peer-pay-cli merchant tier [options]
Options: --tier.
Example:
peer-pay-cli merchant tier --profile live --tier BASE
merchant sandbox
This is a plain API call and does not save or activate a profile. Use peer-pay-cli sandbox enable for the complete flow.
Enable the hosted sandbox for your merchant.
peer-pay-cli merchant sandbox
Example:
peer-pay-cli merchant sandbox --profile live
merchant ip-allowlist
Replace or clear the API key IP allowlist using owner management access.
peer-pay-cli merchant ip-allowlist --profile live --entries 8.8.8.8,1.1.1.0/24
peer-pay-cli merchant ip-allowlist --profile live --clear
peer-pay-cli merchant get --profile live --management
--entries accepts comma-separated public IPv4/IPv6 addresses or CIDRs (at most
50 entries before deduplication). The backend's UpdateApiKeyIpAllowlistSchema
validates locally before sending, canonicalizes addresses and networks, and removes
duplicates. Invalid entries report their index and the backend message, such as
entries.1: Address is not public: "127.0.0.1". IPv4 prefixes must be /8 to /32 and
IPv6 prefixes /32 to /128. Use --clear to send an empty list and remove restrictions;
it cannot be combined with --entries. A missing --entries prompts on a terminal
unless --clear is present; without a terminal it errors with
--entries is required. Run peer-pay-cli merchant ip-allowlist --help.
The request is PUT /api/v1/merchants/me/api-key/ip-allowlist with
{"entries":["8.8.8.8/32","1.1.1.0/24"]} (or {"entries":[]} to clear), using the
management session and selected merchant context. Hosted access requires a Privy
OWNER session. Success returns HTTP 200, message API key IP allowlist updated,
and the merchant profile including apiKeyIpAllowlist. merchant get reads that
field; --management lets the owner inspect it even when API-key access is blocked.
The local route validates with the same schema and returns the hosted HTTP 400
Invalid request envelope with formErrors and fieldErrors on invalid bodies.
The list persists with serve --state FILE and appears in subsequent profile reads.
Each local merchant has its own API key, and a local key also stands in for the
local session identity on management operations; the mock has no Privy sessions
or member role authentication. Invalid local keys still return HTTP 401.
The mock deliberately does not enforce the list against caller IPs. It binds to
loopback, which cannot be allowlisted, and uses the same key for API and management
access. Hosted API-key requests denied by the list return HTTP 403 with
errorCode: "IP_NOT_ALLOWED", message
IP address <ip> is not in this merchant's API key IP allowlist, and
responseObject: { ip, reason: "IP_NOT_ALLOWED" }. An unresolved client IP instead
returns message Client IP could not be resolved for API key IP allowlist enforcement
and responseObject: { ip, reason: "CLIENT_IP_UNRESOLVED" } (ip is the observed
address or null), with the same status and error code. Railway proxy resolution and internal service-token bypass
are hosted behavior, not local simulation.
merchant rotate-key
Replace your API key, show the new key in full once, and save it in the profile.
No --show-secrets flag is needed. Export the saved key later with
peer-pay-cli credentials export --output FILE.
peer-pay-cli merchant rotate-key
Example:
peer-pay-cli merchant rotate-key --profile live
merchant provision-wallets
Retry provisioning missing owner wallets.
peer-pay-cli merchant provision-wallets
Example:
peer-pay-cli merchant provision-wallets --profile live
merchant concierge
Submit a Concierge plan request with the intake questionnaire.
peer-pay-cli merchant concierge [options]
Options: --contact-email (required email), --business-name (optional, trimmed
1 to 200 characters), --telegram (optional, up to 64 characters; maps to
telegramUsername), --message (optional, up to 1000 characters), --answers
(required JSON object, inline or @filename), --data (whole body, inline or
@filename, instead of individual request flags). Omitting businessName uses
the merchant's name on the backend.
On a terminal, omitted --contact-email prompts for the email, then omitted
--answers starts the same nine intake questions documented under
merchant concierge-inquiry, with text input and
fixed-option selectors. All nine answers are required, each a trimmed 1 to 500
character string. Optional flags do not prompt. Supplying both required flags
or a complete --data body runs without prompts, including outside a terminal.
Example (using the answers.json questionnaire below):
peer-pay-cli merchant concierge --profile live --contact-email owner@example.com --answers @answers.json
This sends POST /api/v1/merchants/me/concierge-request using management
authentication; the hosted route requires the merchant owner and records a
pending tier request for admin approval. Hosted requests are limited to 5 per
3600 seconds per merchant; the local mock does not enforce this limit. A pending
tier request already open returns HTTP 409 CONCIERGE_REQUEST_ALREADY_OPEN.
Sandbox requests, including the local mock, validate the questionnaire first:
invalid bodies return HTTP 400; valid bodies return HTTP 403
CONCIERGE_REQUEST_SANDBOX_FORBIDDEN without creating a request.
merchant concierge-inquiry
Submit a Concierge intake inquiry.
Hosted inquiries are limited to 5 requests per 3600 seconds per merchant; the local mock does not enforce this limit.
peer-pay-cli merchant concierge-inquiry [options]
Options: --contact-email (required email), --business-name (optional, trimmed
1 to 200 characters), --telegram (optional, up to 64 characters; maps to
telegramUsername), --message (optional, up to 1000 characters), --answers
(required JSON object, inline or @filename), --data (whole body, inline or
@filename, instead of individual request flags). Omitting businessName uses
the merchant's name on the backend.
On a terminal, omitted --contact-email prompts for the email. When both
--answers and --data are absent, the CLI walks these nine questions in order,
showing each label and hint. Text questions use plain text input; crypto and
feePayer use arrow-key selectors. Every answer is required and must be a trimmed
1 to 500 character string. Optional flags do not prompt. Supplying the required flags
or a complete --data body runs without prompts, including outside a terminal.
| Answer key | Question | Hint / fixed options |
|---|---|---|
business | What do you sell, and what industry are you in? | Your products or services and industry. |
storefront | Where do you sell? | Your website, plus what it runs on: Shopify, WooCommerce, custom site, Telegram, or in person. |
volume | How much volume do you process today? | Daily, weekly, or monthly volume, plus your average order size. Include the currency. |
customers | Where are your customers? | US, UK, EU, or elsewhere. This decides which payment apps we enable. |
apps | Which payment apps do you want your customers paying with? | Venmo, Cash App, Zelle, PayPal, Revolut, Wise, or others. |
payments | What do you use to accept payments today? | Your current payment providers or methods. |
crypto | Do you also want to accept crypto directly? | Customers can pay with any coin on any network and you still settle in USDC. Options: Yes, No, Not sure yet. |
feePayer | Who pays the Peer Pay fee? | Your customer, you, or a split between both. Options: Customer, Merchant, Split. |
integrator | Who is doing the integration on your side, you or a developer? | We will send the right guide and set up an API key if needed. |
Example:
{
"business": "Clothing",
"storefront": "Shopify",
"volume": "USD 50,000 monthly; USD 100 average order",
"customers": "US",
"apps": "Venmo",
"payments": "Stripe",
"crypto": "Not sure yet",
"feePayer": "Merchant",
"integrator": "Our developer"
}
peer-pay-cli merchant concierge-inquiry --contact-email owner@example.com --answers @answers.json
This sends POST /api/v1/merchants/me/concierge-inquiry using management
authentication; the hosted route requires the merchant owner. Unlike
merchant concierge, this route is not sandbox-restricted. The local mock
validates the same body and returns HTTP 201 with a generated inquiry ID:
{
"success": true,
"message": "Concierge inquiry submitted",
"responseObject": { "id": "550e8400-e29b-41d4-a716-446655440000" },
"statusCode": 201
}
merchant shopify
Request Shopify setup for your merchant.
peer-pay-cli merchant shopify --store-domain DOMAIN --collaborator-code CODE --checkout-currency CURRENCY
All three flags are required; interactive terminals prompt for missing values.
--store-domain: Your store's myshopify.com domain, such asacme.myshopify.com.--collaborator-code: The 4-digit collaborator request code from Shopify admin: Settings > Users > Security.--checkout-currency: The 3-letter currency code your Shopify checkout uses, such asUSD.
The CLI and the local sandbox use the backend's validation. The store domain is
trimmed and lowercased, and any leading http:// or https://, path, query, hash and
trailing dot are removed; it must then be a <store>.myshopify.com domain. The
collaborator code must be exactly four digits. The currency is uppercased and must
be an ISO 4217 code.
Invalid fields return these exact messages, in field order:
Enter your store's myshopify.com domainCollaborator request code must be 4 digitsEnter a valid 3-letter currency code
Example:
peer-pay-cli merchant shopify --profile live --store-domain acme.myshopify.com --collaborator-code 1234 --checkout-currency USD
Team
Hosted invitations require merchant management access. Invite requires
--email; --role is MANAGER (default) or CASHIER. List returns
invitations; use merchant get to inspect current members. Revoke takes an
invitation ID; remove takes a member's user ID. Local invitations send no email.
New local state includes an OWNER in merchant get's merchantUsers list;
removing the owner is rejected with HTTP 403.
Each member row reports removalLocked. A locked member cannot be removed:
team remove returns HTTP 403 with errorCode MEMBER_LOCKED, and only an admin
can clear the lock. On hosted merchants, removing a member from the live merchant
also removes their sandbox membership.
Redeem accepts --data @invite.json containing {"token":"INVITE_TOKEN"}.
Keep this file private. After redemption, sign in with --merchant-id to select
the newly accessible merchant. It does not replace the selected profile implicitly.
team list
List merchant invitations.
peer-pay-cli team list
Example:
peer-pay-cli team list --profile live
team invite
Invite a manager or cashier. The default role is MANAGER.
peer-pay-cli team invite [options]
Options: --email, --role.
Example:
peer-pay-cli team invite --profile live --email teammate@example.com --role MANAGER
team resend
Resend an invitation.
peer-pay-cli team resend INVITE_ID
Example:
peer-pay-cli team resend INVITE_ID --profile live
team revoke
Revoke an invitation.
peer-pay-cli team revoke INVITE_ID
Example:
peer-pay-cli team revoke INVITE_ID --profile live
team remove
Remove a team member.
peer-pay-cli team remove USER_ID
Example:
peer-pay-cli team remove USER_ID --profile live
The local invite URL embeds the invite ID as its token:
/local/invites/INVITE_ID/accept. Use that ID with team redeem --token INVITE_ID
to accept the same record as local accept-invite. Local acceptance sends no
email and requires only the API key. Redemption returns {merchantId,role,redeemed}, where role is the member's
resulting role: redeeming consumes the invitation but never changes the role of an
OWNER or a locked member. A retry by an existing member returns redeemed:false
with that member's current role. Unknown/revoked tokens
return 404, expired tokens return 410, and a redeemed token whose membership was
removed returns 409.
team redeem
Accept an invitation with --token or an exclusive JSON body.
For local mock invitations, use the invitation ID as the token.
peer-pay-cli team redeem [options]
Options: --token, --data.
Example:
peer-pay-cli team redeem --profile live --token INVITE_TOKEN
Sub merchant transfers
A Master Merchant Account owner can transfer a sub-merchant it created to a new owner by email. The caller must be OWNER of the sub-merchant and of the Master Merchant Account merchant, and the Master Merchant Account must have Master Merchant Account access enabled. Select the sub-merchant with --merchant-id, or with the profile's merchant.
A transfer link is valid for 7 days, and only one transfer can be pending per sub merchant. While a transfer is pending, sub-merchants set-fee on that sub merchant fails with MASTER_MERCHANT_FEE_LOCKED; cancel the transfer first. When the recipient accepts:
- they become OWNER, and the sub merchant's payout wallets become their wallets;
- the Master Merchant Account keeps a MANAGER seat the new owner cannot remove, with checkout settings and team access; refunds are OWNER-only;
- unpaid orders created before the transfer are cancelled;
- older orders with payment attempts stop accepting new payments.
The link is shown only once, in the transfer send output, and is emailed when sent. Output redacts transferUrl unless --show-secrets is set. To get a new link, cancel the pending transfer (transfer cancel) and send a new one (transfer send). Status never returns the link.
transfer send
Send a sub merchant you own as its Master Merchant Account to a new owner by email. Save the returned transferUrl; it cannot be retrieved again.
peer-pay-cli transfer send [options]
Options: --email, --merchant-id.
Example:
peer-pay-cli transfer send --profile master-merchant --merchant-id SUB_MERCHANT_ID --email buyer@example.com --show-secrets
transfer cancel
Withdraw the pending sub merchant transfer.
peer-pay-cli transfer cancel [options]
Options: --merchant-id.
Example:
peer-pay-cli transfer cancel --profile master-merchant --merchant-id SUB_MERCHANT_ID
transfer status
Read the pending transfer and its open-order impact.
peer-pay-cli transfer status [options]
Options: --merchant-id.
Example:
peer-pay-cli transfer status --profile master-merchant --merchant-id SUB_MERCHANT_ID
transfer accept
Accept a merchant account transferred to your email.
peer-pay-cli transfer accept [options]
Options: --token, --data.
Example:
peer-pay-cli transfer accept --profile pending-transfer --token TRANSFER_TOKEN
Previews the transfer first. Hosted and local previews list exactly CHECKOUT_CONFIG and TEAM in masterMerchantPermissions; the Master Merchant Account manager seat cannot refund after the transfer. If the payout wallet is not delegated, prints the dashboard /transfer link and exits with an error: the CLI cannot set up an embedded-wallet signer. Errors use TRANSFER_NOT_FOUND, TRANSFER_UNAVAILABLE, TRANSFER_EMAIL_MISMATCH, WALLET_SETUP_REQUIRED and TERMS_CHANGED.
Sub merchants
A merchant that an admin enabled as a Master Merchant Account can create sub merchants. A
sub merchant copies the Master Merchant Account's commercial terms and pays out to the Master Merchant Account's wallet
until it is transferred; after that, payouts go to the new owner's wallet. It pays the Master Merchant Account a
master merchant fee on top of Peer's fee, before and after a transfer. Creating a sub merchant and changing a
fee need the Master Merchant Account owner; listing and previewing also work for managers.
sub-merchants create does not select the sub merchant: run merchant switch SUB_MERCHANT_ID to act
on it.
Errors: MASTER_MERCHANT_NOT_ENABLED (403) when the current merchant is not an enabled, LIVE
Master Merchant Account; MASTER_MERCHANT_FEE_EXCEEDS_CAP (422) above your Master Merchant Account cap, when an admin set one;
MASTER_MERCHANT_FEE_EXCEEDS_MAX_FEE (422) when a payment method's configured fees for an
amount band would exceed the sub merchant's max fee; MASTER_MERCHANT_FEE_LOCKED (409) while a
transfer of the sub merchant is pending (cancel it first) or once the sub merchant belongs to
someone else.
Locally, turn on simulatedLive and enable the Master Merchant Account flag first:
peer-pay-cli local settings --data '{"simulatedLive":true}'
peer-pay-cli local admin --data '{"masterMerchantEnabled":true}'
# Optional cap: add masterMerchantMaxFeeBps (0–5000); null means no Master Merchant Account-specific cap.
The local session identity owns every sub merchant it creates. The local mock derives a sub merchant's master merchant fee from its referral split entry for the Master Merchant Account wallet.
sub-merchants list
List the sub merchants you created as a Master Merchant Account, with fee, owner, and transfer state.
peer-pay-cli sub-merchants list
Example:
peer-pay-cli sub-merchants list --profile live
sub-merchants create
Create a sub merchant that pays you a master merchant fee, as the Master Merchant Account owner.
peer-pay-cli sub-merchants create [options]
Options: --sub-merchant-name, --master-merchant-fee-bps, --data.
Example:
peer-pay-cli sub-merchants create --profile live --sub-merchant-name Outlet --master-merchant-fee-bps 100
sub-merchants set-fee
Change the master merchant fee on a sub merchant you still own. The change applies to new orders.
peer-pay-cli sub-merchants set-fee SUB_MERCHANT_ID [options]
Options: --master-merchant-fee-bps, --data.
Example:
peer-pay-cli sub-merchants set-fee SUB_MERCHANT_ID --profile live --master-merchant-fee-bps 150
sub-merchants fee-preview
Preview configured fees per payment method for a master merchant fee. These are configured fees before exchange-rate spread, not Peer's net revenue.
peer-pay-cli sub-merchants fee-preview [options]
Options: --master-merchant-fee-bps.
Example:
peer-pay-cli sub-merchants fee-preview --profile live --master-merchant-fee-bps 100
Onboarding
get reads the backend checklist. ack acknowledges a manual step by
its returned ID; automatically verified steps remain subject to backend evidence.
skip dismisses onboarding without proving an integration is complete.
ack and skip print the updated checklist, the same object get returns.
onboarding get
Read your merchant onboarding checklist.
peer-pay-cli onboarding get
Example:
peer-pay-cli onboarding get --profile live
onboarding ack
Acknowledge an onboarding step. onboarding ack rails confirms payment methods
without completing profile or industry. onboarding ack profile completes the
profile when a business name is saved; a logo is optional. Saving an integration
path also completes the preceding profile, industry, and rails steps.
peer-pay-cli onboarding ack STEP_ID
Example:
peer-pay-cli onboarding ack STEP_ID --profile live
onboarding skip
Dismiss the onboarding checklist.
peer-pay-cli onboarding skip
Example:
peer-pay-cli onboarding skip --profile live
Credentials
Export requires --output FILE. It writes PEER_PAY_API_URL and
PEER_PAY_API_KEY, plus PEER_PAY_WEBHOOK_SECRET when --webhook-id
is given. The signing secret must have been saved by this profile at webhook
creation. Files use mode 0600; existing files are never overwritten.
credentials export
Write credentials to a new private environment file.
peer-pay-cli credentials export [options]
Options: --output, --webhook-id.
Example:
peer-pay-cli credentials export --output .env.peer-pay --webhook-id WEBHOOK_ID
Simulation
simulate and local commands require a local profile. test-order
also works with a hosted sandbox profile, where delivery requires a public HTTPS
handler. It is rejected for live merchants. None of these commands grants hosted
admin access or performs a real payment locally.
Simulation requires ORDER_ID and --event. Specify --payment-id to
select an attempt, --amount for partial fiat settlement/refund/chargeback scenarios,
and --rail when a scenario creates a payment. Non-Apple-Pay crypto settlement
rejects --amount (amountUsdc) with HTTP 400; omit it to settle the full remaining
amount. Apple Pay partial deposits use --deposit-amount. List supported event
names with local events.
Direct refund event injection requires settled funds and progresses from
REFUND_PENDING to REFUND_COMPLETED. Refunds created with refunds create
require a fully fulfilled fiat order and complete with simulate --event REFUND_COMPLETED; their amount is fixed by the simulated deposit. Bridge events progress pending → submitted → completed or
failed. A failed bridge can restart. Quoted, minimum, and actual bridge outputs
use destination token units: 10 USDC on BSC is "10000000000000000000".
Literal destination token addresses are accepted; unlisted tokens use six decimals
in local simulation, matching local quotes. Output amounts are calculated only
for submitted and completed bridge events.
Bridge status flags retain attempt history: after failure → retry → completion,
hasCompletedBridge and hasFailedBridge are both true; canRetryManually is false
because the latest attempt completed. automaticRetry reports scheduledCount,
maxRetries and nextRetryAt like the hosted API; maxRetries is 3 only when serve
runs with PAYMENT_BRIDGE_AUTO_RETRY_ENABLED=true, and 0 otherwise. With it on,
simulate --event PAYMENT_BRIDGE_FAILED --automatic-retry true on a submitted bridge keeps
the same attempt PENDING, sets nextRetryAt from the hosted schedule (10 minutes, 1 hour
and 3 hours after the first failure) and sends no webhook. Inject PAYMENT_BRIDGE_SUBMITTED
to resubmit it: the simulator does not wait for nextRetryAt. After three scheduled
retries, or with automatic retry off, the failure is terminal: PAYMENT_BRIDGE_FAILED is
sent and its message ends with (after N automatic retries) when retries happened. Like
the hosted API, failure fields appear only on failed attempts: a waiting attempt shows
failureCode and failureMessage as null in latestAttempt, attempts and bridgeInfo.
Webhook timing is in
bridge failures and automatic retries.
Chargebacks require settled payments.
Invalid transitions fail before changing state. After a failed, expired, or
cancelled attempt, create a new payment to retry the order. Local and hosted
sandbox also permit a new attempt while another is CREATED. For Zcash, they return
the existing deposit without a new PAYMENT_CREATED while it is CREATED,
unexpired and quoted for the current remaining amount. An expired or differently
priced deposit is cancelled without a webhook and replaced. A
--replacement-payment-id (or replacementPaymentId in payments create --data) leaves the
previous sandbox attempt unchanged; the live API validates and cancels the
previous fiat attempt. The local hosted order read returns the newest payment,
except when it was cancelled, expired or failed and an older fiat attempt still
has an open, unexpired on-chain intent; then it returns the newest such attempt
within the ten newest payments. If the newest attempt is a cancelled, expired or
failed fiat attempt and no older fiat intent is open, it returns the newest settled
attempt when one exists in that history; a cancelled, expired or failed crypto
attempt stays current. A funded Zcash deposit takes priority while
converting. A newest active or settled attempt stays current over older fiat intents.
Coinbase Apple Pay considers only the latest payment
by creation time, regardless of status, and replays it only when it is CREATED,
unexpired, has the same Apple Pay provider, rail, currency, and remaining order
amount. Thus Venmo → Apple Pay → Apple Pay creates two payments, while Apple
Pay → Venmo → Apple Pay creates three.
Settlement marks an order FULFILLED when its remaining amount is at most its
completion threshold (see Orders), preserving the residual amount.
Successful crypto sandbox simulation returns actualDestinationAmount as the
persisted net settlement after fees, in destination token units, on both the first
response and replay. For a 10 USDC payment with a 5% merchant-paid fee on Base,
this is "9500000" (9.5 USDC); expectedDestinationAmount remains the quote amount.
Fiat platform quotes exclude all crypto rails, including Zcash, and disabled
rails. They also leave out a fiat rail whose payment would sign more than
maxOrderAmountUsdc with buyer-paid fees, for fixed orders and open-amount
drafts alike, so a payment start on it would return INTENT_ABOVE_MAX (see
payments create). Platform and payment quotes both derive sellerAutomatedReleaseAvailable
from the backend sandbox SAR eligibility rule (isSarEligibleFiatRail), using
the local mock's empty SAR-disabled rail list. In-person Venmo orders therefore
expose a selectable platform. Ordinary orders retain their rails and quote amounts.
test-order --data '{"rail":"VENMO","requestedUsdcAmount":"10"}' normalizes the rail to lowercase.
Test orders consider only supported, runtime-enabled merchant rails. Without an
explicit rail, they prefer the first fiat rail, then the first crypto rail;
[near_intents_133701, venmo] selects Venmo. No usable rail returns HTTP 400 with
Merchant has no enabled rail to create a test payment on. An unavailable explicit
rail returns HTTP 400 with Requested rail is not supported and enabled for this merchant.
Delivery attempts against an inactive webhook increment attempts and set
lastAttemptAt before failing. Transport failures retain the prior HTTP response
code and store a local failure reason in responseBody in persisted state; like
the backend, delivery-list responses omit that internal field. The webhook test
response returns that reason as error only when no HTTP response code exists;
an inactive webhook returns error: null.
local settings accepts stakingFixture (boolean; seed/reset deterministic staking rows), rates (currency-to-USD fixture rates expressed as
currency units per USD), quoteTtlSeconds, logoUrl (a URL, or null to
clear the mock merchant logo), minOrderAmountUsdc and maxOrderAmountUsdc
(positive decimal strings with up to six decimal places). The default rates
cover every currency the checkout currency selector offers, plus USDC, USDT and
ZEC. A state file saved earlier keeps its own rates. The local minimum
defaults to 1 USDC to match production; raise it to simulate stricter
environments. The local maximum defaults to 10,000 USDC, the hosted per-order
limit. The maximum must stay above the minimum and below 1 trillion USDC;
otherwise the request returns 400 and changes nothing. State files saved before
this setting existed load with the 10,000 USDC default. These persisted settings
apply to new orders, resizes and resize simulations. local admin accepts the
merchant admin config body, including fee overrides, branding, and order-size
limits. local retry restarts a finished delivery's retry budget;
local accept-invite accepts an invitation without sending email and, like team redeem, never changes the role of an OWNER or a locked member.
simulate
--event DISPUTE_OPENED, DISPUTE_ESCALATED and DISPUTE_PAID need a settled fiat --payment-id.
DISPUTE_OPENED takes an optional --amount (the gross USDC release; defaults to the payment's net plus fees).
The local mock enforces the hosted transitions: one dispute per payment; only OPEN escalates; PAID is final.
simulate injects local mock events. For the hosted sandbox API, also served
locally, use sandbox crypto-simulate, sandbox fail-payment, or
sandbox expire-payment.
Apply an event to a local order. No money moves. Non-Apple-Pay crypto rejects
--amount; Apple Pay deposits use --deposit-amount. Bridge outputs use the
destination token decimals, and status flags retain attempt history.
peer-pay-cli simulate ORDER_ID [options]
Options: --event, --payment-id, --amount, --amount-currency, --deposit-amount, --rail, --automatic-retry.
--event ORDER_AMOUNT_SET needs --amount, --amount-currency and --rail; a fiat rail is quoted in --amount-currency.
ORDER_RESIZED uses the local settings minOrderAmountUsdc floor, which
defaults to 1 USDC to match production, and the maxOrderAmountUsdc ceiling,
which defaults to 10,000 USDC; an amount above it returns HTTP 400
AMOUNT_ABOVE_MAX. On an open-amount order it returns HTTP 400
RESIZE_NOT_SUPPORTED.
ORDER_AMOUNT_SET takes --amount, --amount-currency (the currency the amount
is typed in) and --rail. Like the hosted API, it fires only when a payment
starts with a new amount, so the simulation starts that payment exactly as
payments create --amount --amount-currency does: it saves the amount, sends
ORDER_AMOUNT_SET, then PAYMENT_CREATED, and returns the order and the
payment. The amount or its currency must differ from the saved one, or the
simulation returns HTTP 409; locked, stale and out-of-range amounts return the
codes listed under Orders, and a fiat rail whose fee-inclusive amount
is above maxOrderAmountUsdc returns INTENT_ABOVE_MAX without saving the
amount.
Crypto settlement accepts payments in CREATED or EXPIRED, including expired
Apple Pay attempts. Zcash also accepts CANCELLED payments for late deposits.
Repeated settlement of SETTLED is idempotent. FAILED crypto payments and
CANCELLED non-Zcash payments return HTTP 409 with
Payment is not open for sandbox simulation and no error code.
Successful late settlement clears the payment error and updates the order's
remaining amount and fulfillment status, including a previously cancelled order.
Non-Apple-Pay crypto settles the full remaining amount and emits PAYMENT_SETTLED
and ORDER_FULFILLED once; Apple Pay uses its quoted deposit contribution and
may leave the order partially fulfilled.
Example:
peer-pay-cli simulate ORDER_ID --event PAYMENT_SETTLED --payment-id PAYMENT_ID
test-order
Use --order-id to fulfill an existing sandbox order, --amount for requestedUsdcAmount, and --rail to select a rail. Omit all flags for backend defaults.
Create and fulfill a test order in local or hosted sandbox mode. The default
test amount is 1 USDC. The local minimum defaults to 1 USDC to match production.
If you raise minOrderAmountUsdc through local settings to simulate a stricter
environment, supply requestedUsdcAmount at or above that minimum. An existing
orderId does not create a new order. Without an explicit rail, the command
chooses the first supported, runtime-enabled fiat rail, then the first crypto rail.
peer-pay-cli test-order [options]
Options: --order-id, --amount, --rail, --data.
Example:
peer-pay-cli test-order --profile live-sandbox
local reset
Reset the selected local simulator state file without parsing it. This works for
incompatible files from an older CLI version. Stop the server using that file
first: a held state lock refuses the reset. The command renames the existing file
to PATH.backup-<uuid> and prints the state and backup paths; it does not delete
the backup or contact the hosted API. No legacy data is converted.
peer-pay-cli local reset --state PATH
--state is required and must name an existing file. Restart peer-pay-cli serve
with the same --state path to initialize fresh state. Orders, payouts, settings
and webhook history from the old file remain only in the backup.
Example:
peer-pay-cli local reset --state ./local-state.json
local events
List all supported simulation events.
peer-pay-cli local events
Example:
peer-pay-cli local events
local settings
simulatedLive (true or false) defaults to on for new local merchants and makes the selected merchant count as non-Sandbox for payout creation and the Master Merchant Account checks. Turn it off to get the hosted Sandbox refusals, such as 403 PAYOUT_SANDBOX_UNSUPPORTED. Responses still report SANDBOX, and every other path behaves as sandbox. Existing state files keep their stored value.
peer-pay-cli local settings --data '{"simulatedLive":true}'
Set mock staking fixtures (stakingFixture), exchange rates, payment expiry,
logo (logoUrl), and the persisted local minimum and maximum.
minOrderAmountUsdc accepts a positive decimal string with up to six decimal
places and defaults to 1 USDC to match production. Raise it to simulate stricter
environments for order creation and resizing. maxOrderAmountUsdc takes the same
format and defaults to 10,000 USDC, the hosted per-order limit. It caps fixed
orders, resizes, the open-amount range and the fee-inclusive amount a fiat
payment signs (INTENT_ABOVE_MAX), must stay above minOrderAmountUsdc
and below 1 trillion USDC, and is checked against the pair after the patch, so
both can change in one request.
peer-pay-cli local settings [options]
Options: --data.
Example:
peer-pay-cli local settings --data '{"quoteTtlSeconds":30}'
peer-pay-cli local settings --data '{"minOrderAmountUsdc":"10"}'
peer-pay-cli local settings --data '{"maxOrderAmountUsdc":"500"}'
local admin payout
Show or set Peer's payout fee, step, rails and tokens for the local merchant.
peer-pay-cli local admin payout [options]
Options: --data.
Local profiles only. Without --data it reads, and with it saves, the local merchant through the
same routes the admin app uses: GET and PATCH /api/v1/admin/cashouts/merchants/MERCHANT_ID/config.
The response has the Peer fee (peerFeeBps), the payout step (payoutStepUsdc) and its range
(payoutStepRange), the merchant's rails (rails), customer support link (supportUrl), funding-token
limit (fundingTokens), the rails customers can use (effectiveRails), and the merchant fee and wallet
(merchantFeeBps, merchantFeeRecipient). rails is the same list the merchant edits in its dashboard;
effectiveRails removes Peer’s global and runtime disables. Both lists hold the per-network
rail ids in rail order. A body sets any of:
{ "peerFeeBps": 150, "payoutStepUsdc": 50, "rails": ["relay_8453"], "fundingTokens": ["8453:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"] }
peerFeeBps is 0–1000; nonzero rates require a Concierge merchant. Base, Pro, and merchants without a selected plan must use 0. A nonzero rate on a self-serve merchant returns 400 PAYOUT_PEER_FEE_REQUIRES_CONCIERGE.
payoutStepUsdc is a whole PAYOUT_MIN_USDC–1000, rails any of venmo,
cashapp, paypal, zelle, revolut, chime, a supported relay_<chainId> network id or near_intents_133701 (the same
list the merchant edits; crypto is refused), and
fundingTokens "chainId:lowercaseAddress" keys Pay supports (empty means every token Peer
allows). The CLI adds "actor": "peer-pay-cli" when the body names no actor. A save that changes
nothing writes no history. A body with no editable field (including one with only actor)
returns 400 "Nothing to change". Changes apply to new payouts only.
Any rail can be stored, including one Peer has disabled globally; effectiveRails leaves a
disabled rail out, and new payouts don't offer it. A missing customer support link never
refuses a save.
Example:
peer-pay-cli local admin payout --data @merchant-payout.json
local admin payout-settings
Show or set the global payout rails, funding tokens and Peer fee wallet.
peer-pay-cli local admin payout-settings [options]
Options: --data.
Local profiles only; GET and PATCH /api/v1/admin/cashouts/settings. These apply to every
merchant: a rail or token switched off here is off for all of them, and the Peer fee on new payouts
goes to peerFeeRecipient (orders still use DEFAULT_FEE_RECIPIENT). The response also lists
supportedFundingTokens, every token Pay can route. disabledRails uses the same fiat and
per-network rail ids as rails and effectiveRails; crypto is refused. Adding relay_42161
turns Arbitrum off for new payouts. Existing payouts keep their stored rails. A body with no
editable field returns 400 "Nothing to change".
Currencies come from the rail catalog: PayPal and Revolut pay all their listed currencies; other fiat rails pay USD. A settings body sets any of:
{ "disabledRails": ["paypal"], "disabledFundingTokens": [], "peerFeeRecipient": "0x…40 hex" }
Example:
peer-pay-cli local admin payout-settings --data @global.json
local admin
masterMerchantEnabled and masterMerchantMaxFeeBps (0–5000) follow the hosted rules: enabling needs a simulated LIVE merchant; the cap is optional and null means no Master Merchant Account-specific cap. A sub merchant cannot be a Master Merchant Account (MASTER_MERCHANT_INELIGIBLE), and a sub merchant's Master Merchant Account split entry changes only through the master merchant fee (MASTER_MERCHANT_FEE_MANAGED).
Set mock merchant admin controls, including fee overrides.
peer-pay-cli local admin [options]
Options: --data.
Example:
peer-pay-cli local admin --data @admin.json
local retry
Retry a completed local webhook delivery.
peer-pay-cli local retry DELIVERY_ID
Example:
peer-pay-cli local retry DELIVERY_ID
local accept-invite
Accept a simulated invitation without sending email.
peer-pay-cli local accept-invite INVITE_ID
Example:
peer-pay-cli local accept-invite INVITE_ID
local session
Act as another local identity, such as a transfer recipient. The identity is created on first use with a deterministic simulated wallet. --wallet-delegated false simulates a recipient whose payout wallet is not delegated yet. The local API key keeps authenticating; identity-scoped routes (/auth/*, transfer preview and accept) act as the session identity. Local transfer links point at http://localhost:5175/transfer.
peer-pay-cli local session [options]
Options: --email, --wallet-delegated.
Example:
peer-pay-cli local session --email new-owner@example.com
API requests
Use api for endpoints or query filters without a dedicated command.
METHOD must be GET, POST, PUT, PATCH, or DELETE. PATH must start with
/api/v1/ (or /local/ for a local profile); requests cannot change the
profile's origin and do not follow redirects. The command expects a JSON API
response. --management uses the signed-in session instead of the API key.
api
Call an API endpoint. Use --management for owner or manager authentication.
peer-pay-cli api METHOD PATH [options]
Options: --data, --management.
Example:
peer-pay-cli api GET '/api/v1/merchants/me/orders?limit=20'
Recent merchant APIs
orders payments
List all payment attempts for an order.
peer-pay-cli orders payments ORDER_ID [options]
Options: --management.
Example:
peer-pay-cli orders payments ORDER_ID --profile live
payments recreate
Request a replacement for a failed, expired, or cancelled fiat payment.
peer-pay-cli payments recreate ORDER_ID PAYMENT_ID [options]
Options: --idempotency-key (8 to 200 characters), --management.
Example:
peer-pay-cli payments recreate ORDER_ID PAYMENT_ID --profile live --idempotency-key retry-001
payments extend
Request an extension for an eligible fiat payment window.
peer-pay-cli payments extend ORDER_ID PAYMENT_ID [options]
Options: --idempotency-key (8 to 200 characters), --management.
Example:
peer-pay-cli payments extend ORDER_ID PAYMENT_ID --profile live --idempotency-key extend-001
payments fulfill-sar
Request seller-verified fulfillment using a transaction ID.
peer-pay-cli payments fulfill-sar ORDER_ID PAYMENT_ID [options]
Options: --tx-id, --idempotency-key (8 to 200 characters), --management.
Example:
peer-pay-cli payments fulfill-sar ORDER_ID PAYMENT_ID --profile live --tx-id TRANSACTION_ID --idempotency-key fulfill-001
actions get
Read a remediation action and its current payment snapshot.
peer-pay-cli actions get ORDER_ID ACTION_ID [options]
Options: --management.
Example:
peer-pay-cli actions get ORDER_ID ACTION_ID --profile live
quotes availability
First-class flags map to amount, quoteMode, enabledRails, fiatCurrency, destinationChainId, destinationToken, destinationAddress, and nearbyQuotesCount (1 to 10, default 3). Amount, quote mode, and all three destination fields are required.
peer-pay-cli quotes availability --amount 10 --quote-mode exact-token --destination-chain-id 8453 --destination-token USDC --destination-address WALLET_ADDRESS
Check fiat quote availability for the requested rails and amount.
peer-pay-cli quotes availability [options]
Options: --amount, --quote-mode, --enabled-rails, --fiat-currency, --destination-chain-id, --destination-token, --destination-address, --nearby-quotes-count, --data.
Example:
peer-pay-cli quotes availability --amount 10 --quote-mode exact-token --destination-chain-id 8453 --destination-token USDC --destination-address WALLET_ADDRESS
merchant referral
Read your referral code, reward recipient, and referred merchants.
peer-pay-cli merchant referral [options]
Example:
peer-pay-cli merchant referral --profile live
Remediation commands use your merchant API key by default. Pass --management
to use an owner or manager session. Create requests return an action ID and status;
a 202 response means accepted, not fulfilled. Poll with actions get until the
status is SUCCEEDED, BLOCKED, or FAILED, and inspect the returned payment.
Reuse the same --idempotency-key (8 to 200 characters) when retrying the same action.
--tx-id is the payment platform transaction ID, not an onchain hash.
Recreate applies to eligible failed, expired, or cancelled fiat payments. Extend
requires an open intent within six hours of expiry. Fulfill SAR requires an
eligible seller-verifiable rail; the backend checks eligibility and approvals.
Local and hosted sandbox remediation mutations return SANDBOX_NOT_SUPPORTED.
Use simulate for local lifecycle tests. See remediation API
for statuses, eligibility, rate limits, and errors.
merchant referral requires an owner or manager session and a live merchant
with a payout wallet. It returns the code, referralFeeBpsByTier
({ "BASE": 30, "PRO": 50, "CONCIERGE": 100 }), recipient, total referred
count, and the newest 50 referred merchants. Rates follow the referred merchant's
tier, regardless of the referrer's tier. Local and hosted sandbox
profiles return REFERRAL_SANDBOX_FORBIDDEN. Apply someone else's code when
creating your account with login --referral-code CODE; existing merchants are
not retroactively attributed.
For quotes availability, use a JSON body such as:
{
"amount": "10",
"quoteMode": "exact-token",
"enabledRails": ["venmo"],
"destinationChainId": "8453",
"destinationToken": "USDC",
"destinationAddress": "0x0000000000000000000000000000000000000001"
}
enabledRails scopes the check to the checkout's rails; omit it to use merchant
settings. Crypto-only requests report no fiat availability. Local results are
synthetic and do not represent live liquidity. As hosted, an amount above
maxOrderAmountUsdc (converted at the fixture rate in exact-fiat mode) returns
HTTP 400 AMOUNT_ABOVE_MAX, and a rail whose local signal amount, buyer-paid
fees included, is above maxOrderAmountUsdc is not counted. See quote availability.
Known local simulator limitations: it does not reproduce dashboard roles, admin authorization, API-key IP allowlist enforcement, backend serviceability, live liquidity, fee-sensitive quoting, or nearby quote suggestions.
Local fiat-denominated orders divide the requested fiat amount by the fixture
currency-per-USD rate, then round half-up to two USDC decimal places. USD inputs
are rounded directly. Order fields omit trailing fractional zeros ("10", "10.5");
metadata.requestedUsdcAmountAtCreation retains two decimal places ("10.00", "10.50").
For example, 10 EUR at 0.92 EUR/USD becomes 10.87 USDC;
payment quotes and webhook order amounts use that rounded value. A fee-free
EUR settlement records 10.0004 EUR (10.87 × 0.92).
Local crypto quotes use shared per-chain token decimals for both origin and
destination amounts. A 10 USDC payment from BSC to Base quotes
originAmount: "10000000000000000000" and destinationAmount: "10000000".
Arc USDC deposits (relay_5042) and payouts (destinationChainId: "5042")
use the six-decimal ERC-20 interface, so 10 USDC is "10000000" in either
direction. Enable relay_5042 for deposits and payout conversion for Arc payouts.
Conversion uses fixture token units per USD; configure missing tokens with
local settings --data '{"rates":{"ETH":"0.0004","BTC":"0.000013"}}'.
For example, a 10 USDC quote costs 0.004 ETH at that fixture rate. As in hosted
sandbox quotes, origin amounts round to token precision and then truncate to
six decimal places, except ZEC, which retains eight. Base USDC and the default
ZEC fixture quotes keep their existing values. These are deterministic test
rates, not market prices.
Crypto settlement records paymentAmount from the persisted quote's
originAmount, formatted in the origin token's decimals (eight for ZEC), even
if fixture rates change afterward. For example, a 10 USDT destination at a
0.8 USDT/USD fixture rate quotes 12.5 USDC from Base and settles with
paymentAmount: "12.5". Fiat settlement continues to use the gross USDC amount
multiplied by the payment's currency-per-USD rate. Apple Pay continues to use the
actual USDC deposit amount.
Test Coinbase Apple Pay funding
Apple Pay is an ordinary payment method in enabledRails, separate from fiat and
crypto rails. The admin-only applePayAvailable flag defaults to false and
allows a merchant to enable apple_pay. Orders copy the resolved enabled rails;
changing merchant enablement later does not enable Apple Pay on an existing order.
Apple Pay uses relay_8453 USDC for funding and pays out in the order's
destination token, like any other Relay crypto payment.
Pricing uses the apple_pay entry in railPlatformReferralFeeConfig, falling back
to platformReferralFeeConfig, including tranches. { "mode": "exempt" } sets no
platform fee without disabling Apple Pay. The local simulator snapshots these
configs and referral splits when the order is created.
Fiat quote availability excludes apple_pay; its fee override never changes fiat
platform quotes or their counts. Apple Pay settles through the Relay funding path.
A literal rail: apple_pay payment is rejected as a rail that is not enabled.
Use rail: relay_8453, currency: USDC, and onrampProvider: coinbase_apple_pay.
Sandbox platform lists exclude Apple Pay. Automatic sandbox test orders choose a fiat rail first, then a crypto rail; an Apple Pay-only configuration has no rail for this automatic payment test.
Apple Pay tranche fees resolve twice, including after PAYEE gross-up, just as fiat fees do. For example, 95 USDC at 100 bps can cross into a 1000 bps tranche; the simulator then requests 105.555556 USDC to deliver 95 USDC.
When Apple Pay is unavailable, new orders omit it from their resolved rails; other
payment methods remain usable. An order left with no payable method returns
NO_ELIGIBLE_PAYMENT_RAILS. Admin rail updates strip Apple Pay unless availability
is granted. Merchant settings reject a new enable attempt without availability,
but accept an unchanged stored selection and clear it during the save.
When the stored quote preference is EXCLUSIVE_SAR, merchant config PATCH and PUT remove
non-SAR fiat rails (for example, ["venmo","revolut"] becomes ["venmo"])
before checking Apple Pay availability and saving. Available apple_pay is
preserved. PATCH uses the stored selection when enabledRails is omitted;
supplied rails replace the selection in both verbs.
For an effective quote preference other than EXCLUSIVE_SAR, an admin PATCH that
touches neither availability nor the rail list preserves the stored selection,
including apple_pay when availability is false. With EXCLUSIVE_SAR (including
when set in that PATCH), admin updates first filter non-SAR fiat rails from the
supplied or stored selection, then strip Apple Pay if availability is false.
Available Apple Pay is preserved. Explicitly setting applePayAvailable: false
strips Apple Pay from the resulting selection in every mode.
peer-pay-cli local admin --data '{"applePayAvailable":true,"railPlatformReferralFeeConfig":{"apple_pay":{"mode":"flat","currency":"USDC","valueBps":500}}}'
peer-pay-cli merchant settings --data '{"enabledRails":["venmo","relay_8453","apple_pay"]}'
peer-pay-cli orders create --amount 10
peer-pay-cli payments create ORDER_ID --rail relay_8453 --currency USDC --onramp-provider coinbase_apple_pay
peer-pay-cli simulate ORDER_ID --event PAYMENT_SETTLED --payment-id PAYMENT_ID --deposit-amount 5
peer-pay-cli orders get ORDER_ID
--deposit-amount is a USDC funding amount for a successful local Apple Pay
simulation. Omit it to fund the full quote. It cannot be combined with --amount.
Net Apple Pay settlement uses destination token decimals. A 10 USDC deposit with
a 500 bps fee to BSC USDC records netSettledUsdcAmount: "9.5" and returns
actualDestinationAmount: "9500000000000000000"; order accounting remains in USDC.
Partial deposits leave an order balance for another payment. Replaying settlement
does not credit a deposit twice. Local testing models the payment and fee contract;
it does not run Coinbase, card authorization, or an Apple Pay sheet.
The API form accepts paymentId, outcome, and optional depositAmountUsdc at
POST /api/v1/orders/ORDER_ID/crypto/sandbox/simulate. Supplying paymentId rejects
simulation if that payment has been replaced. Deposits must be positive and no
larger than the persisted quote, and partial deposits require Apple Pay success.
Payment objects and webhooks now include penalties, an array of applied payment
reductions. Local payments without an attestation return []. Live penalty entries
contain kind, penaltyBps, originalAmount, and attestedAmount; see the
payment API.
Local state uses format version 3, which holds several merchants and identities.
serve --state FILE validates the saved state before starting. Invalid JSON and
invalid version 3 contents report the state file path; repair the file or start
with a new --state path. A version 2 file is rejected with
uses state format version 2; this peer-pay-cli uses version 3: start with a new
--state path and pass --api-key to keep the same local API key. A file written
by a newer peer-pay-cli asks you to upgrade. Saved hosted profiles are unaffected.
Loading an older state file rewrites stored crypto: rails and disabledRails expand to the twelve
relay_* rails, payoutRail and attempt crypto become relay_8453 with Base USDC, and stored
webhook payloads are rewritten too. Old webhook snapshots backfill settlement.refundFeeAmount
as "0.00"; a payoutTransfer missing usdcTxHash gets decimals: 6, usdcTxHash from its
txHash, and provider: null. Retired NEEDS_SUPPORT timeline rows are dropped on load.
Local JSON requests over 100 KB return HTTP 413; malformed or non-local Host headers return HTTP 403. Background delivery and payment-expiry errors are logged to stderr. A failed delivery save clears the in-flight marker so later attempts can run. Shutdown waits for active webhook deliveries, including connectivity tests, before saving state and releasing the state-file lock.
whoami
Read the signed-in profile, accessible merchants, defaultMerchantId, and staking. Locally, lists the merchants the local session identity belongs to; serve seeds one, with that identity as OWNER.
peer-pay-cli whoami --profile live
merchant switch
Select an accessible merchant and refresh its API key. Hosted profiles only; changing merchants clears cached order tokens and webhook secrets.
peer-pay-cli merchant switch MERCHANT_ID --profile live
merchant stats
Read merchant dashboard statistics.
peer-pay-cli merchant stats --profile live
merchant tier-usage
Read merchant tier usage.
peer-pay-cli merchant tier-usage --profile live
merchant bridge-signer-status
Read bridge signer status using the management session.
peer-pay-cli merchant bridge-signer-status --profile live --signer-id SIGNER_ID --chain-type ethereum
Options: --signer-id, --policy-id, --chain-type.
orders support-links
Read support links for a comma-separated list of orders.
peer-pay-cli orders support-links --profile live --order-ids order_a,order_b
Options: --order-ids.
integration runbook
Print the public integration runbook as Markdown text.
peer-pay-cli integration runbook --profile live
The runbook command prints plain text, including when piped; --json encodes
that text as a JSON string.
whoami uses a hosted management session or the local API key.
merchant switch requires a hosted profile. Switch checks
accessibleMerchants using the target merchant on both /auth/me and login, so
revoked access to the current merchant does not prevent switching. A target that
returns 403/404 or is absent from accessibleMerchants reports
You do not have access to merchant <id>. Switch saves the selected merchant and refreshed
API key together. Failed selection or login leaves the profile unchanged. Roles
whose login response masks the API key cannot use API-key commands after switching;
they can use management commands. No wallet provisioning occurs during switch.
The local simulator supports merchant stats, tier usage, bridge signer status,
order support links, and sandbox enablement. Its state holds merchants keyed by
ID and identities keyed by Privy user ID and email; serve seeds one merchant
whose OWNER is the local session identity owner@example.com. Each merchant's
API key selects that merchant. X-Merchant-Id naming another merchant selects it
when the session identity is a member and otherwise returns HTTP 403
Merchant access not found, like the hosted dashboard; /auth/me returns HTTP
404 Merchant not found for such an ID and /auth/login falls back to the oldest
membership. Without X-Merchant-Id, both auth routes select the oldest membership.
The response's accessibleMerchants list is sorted by role (OWNER, MANAGER, CASHIER),
then by merchant name within each role; that order does not determine default selection.
Orders, payments, refunds, webhooks, invites, members,
config and idempotency keys are isolated per merchant. Privy login and role
restrictions are not simulated.
Local merchants report SANDBOX and the fixture CONCIERGE tier. Consequently,
merchant tier returns HTTP 403 TIER_CHANGE_SANDBOX_FORBIDDEN, and
merchant concierge returns HTTP 403 CONCIERGE_REQUEST_SANDBOX_FORBIDDEN.
Neither rejection changes state. Wallet provisioning returns the existing fixture
addresses; PUT /api/v1/merchants/me/users reports Merchant user updated and returns the merchant profile; like the hosted route it never creates a membership or changes a role, and it returns HTTP 404 Merchant user not found when the session identity has no membership.
merchant sandbox persists an idempotent pair using the same local merchant ID.
Onboarding acknowledgements, skip/completion timestamps, and Shopify setup requests
persist in the state file. Unknown step IDs are rejected. requestSetup directs you to
peer-pay-cli merchant shopify; acknowledging an automatic step does not satisfy its facts.
Shopify setup records the normalized store domain, collaborator code and checkout
currency in a local request and acknowledges requestSetup; it does not contact
the Peer team. Invalid values are rejected with the backend's messages and change
nothing. Requests saved before details were collected load with all three fields
null. Completion is derived and saved whenever the checklist is returned (get,
ack, skip); skipping only dismisses the checklist. Local orders never satisfy
live-order steps. The onboarding sandboxApiKey is non-null only for CUSTOM_API,
WOOCOMMERCE, and TELEGRAM integration paths.
Dashboard stats count fulfilled and partially fulfilled orders separately; volume
and net receipts include only fulfilled orders (receipts sum their settled
payments). Monthly tier usage counts fulfilled and partially fulfilled orders only
when they have at least one SETTLED fiat-rail payment (including bank variants such
as zelle-chase). Each qualifying order counts once at its full requested USDC
amount, including mixed fiat/crypto orders, by order creation time in the current
UTC calendar month (inclusive start, exclusive next-month start). Crypto-only orders
and orders with no settled fiat payment do not count. The response shape remains
{ tier, limits, usage: { orderCount, volumeUsdc }, windowStart, windowEnd }.
Sandbox usage has no tier caps. Order and payment lists include total, page,
and limit alongside their rows.
Bridge signer status validates the hosted query and reports no attached signer
for the EVM fixture (walletId: "", attached: false, additionalSigners: []).
The fixture has no attached Solana bridge signer; Solana signer queries return
HTTP 400 even though a payout wallet is provisioned. Support links
return a map keyed by owned order IDs, with deterministic local_<orderId> thread
IDs, hasMessages: false, and status: "AI_HANDLING"; no real support thread is
created. Repeated and comma-separated order IDs are accepted, deduplicated, and
filtered to existing local merchant orders. The public integration runbook works
locally.
Staking, refunds, sandbox controls, and settlement amounts
Staking and refunds use the hosted management session (Bearer and merchant ID), or the local API key. Hosted refunds need the owner's session; managers and cashiers get 403. The local mock deliberately provides simulated staking and refunds even though the hosted sandbox disables them. Bridge inspection uses the management session on hosted profiles and the API key locally. Sandbox controls use the API key and attach a cached order token if present, but do not require one.
simulate is local mock event injection. sandbox ... calls the hosted sandbox
API, whose outcome routes are also served by the local mock. Sandbox commands
require sandbox orders; use a sandbox profile for hosted testing.
Local staking starts with zero stake and empty lock/activity pages. Seed one active 10 USDC lock and two activity entries (a 100 USDC deposit and its lock):
peer-pay-cli local settings --data '{"stakingFixture":true}'
peer-pay-cli staking overview
peer-pay-cli staking locks --status ACTIVE
peer-pay-cli staking activity --limit 1
The staking taker, effective stake owner, and every lock and activity owner are the selected merchant's checksummed v1 EVM wallet, as hosted.
While seeded, the lock page also lists one ACTIVE lock for each of the selected
merchant's local fiat payments that is CREATED, EXPIRED, or SETTLED. Its lockId is the payment's
intent hash, its amount is the intent amount, and it is linked to the payment as
a PAY_PAYMENT lock, as hosted does. Unsettled payment locks show maturesAt
0 (awaiting settlement). Cancelled and failed payments hold no lock. The
overview's lock totals and locked stake count these locks, while free stake
stays at 90 USDC.
Use stakingFixture:false to reset the fixtures. Amounts use USDC base units.
Pages match hosted {items,nextCursor,latestUpdateBlock}; there is no hasMore
field. Pass the returned cursor with the same filter to read the next page;
nextCursor:null marks the end. Fixture data is deterministic and persisted.
These addresses, balances, and access flags do not represent onchain stake.
staking overview
Read merchant staking balances and access using the management session, or deterministic local fixtures.
peer-pay-cli staking overview [options]
Example:
peer-pay-cli staking overview --profile live
staking locks
List merchant stake locks using the management session, or local fixtures.
peer-pay-cli staking locks [options]
Options: --status, --cursor, --limit.
--status is ACTIVE, UNLOCKED, or RESOLVED. --cursor is an opaque cursor from the previous page. --limit is 1 to 100, default 50.
Each lock's association is either {"kind":"EXTERNAL"} or a PAY_PAYMENT
link with paymentId, orderId, and releasesIfUnpaidAt. releasesIfUnpaidAt
is the ISO 8601 UTC time the lock frees itself if the customer never pays: the
payment's quoteExpiresAt plus 30 minutes, when Peer cancels the expired
intent. CREATED payments, and EXPIRED payments whose intent has not been
cancelled yet, get that time. It is null once the payment is SETTLED,
CANCELLED, or FAILED, once the intent has been cancelled, while automatic
cancellation is switched off, or when the payment window closed more than 24
hours ago. The local mock never cancels intents, so a local EXPIRED payment
keeps its time until that 24-hour limit.
Example:
peer-pay-cli staking locks --profile live --status ACTIVE --cursor CURSOR --limit 20
staking activity
List merchant stake activity using the management session, or local fixtures.
peer-pay-cli staking activity [options]
Options: --kind, --cursor, --limit.
--kind is DEPOSITED, WITHDRAWN, TAKER_AUTHORIZATION_UPDATED, STAKE_OWNER_SELECTED, LOCK_FUNDED, STAKE_LOCKED, STAKE_LOCK_INCREASED, STAKE_LOCK_RESIZED, STAKE_UNLOCKED, STAKE_LOCK_RESOLVED, CLAIM_CREATED, or CLAIM_WITHDRAWN. --cursor is opaque; --limit is 1 to 100, default 50.
Example:
peer-pay-cli staking activity --profile live --kind DEPOSITED --limit 20
Local merchant refunds require a FULFILLED order with one settled, supported fiat payment, a Base USDC destination, and no recorded chargeback. Refund pricing uses the local currency rate discounted by 1%, rounded as in the hosted service. The merchant must fund that refund amount in the hosted flow; locally no funds move.
peer-pay-cli refunds create ORDER_ID --seller-username seller123
peer-pay-cli refunds get ORDER_ID
peer-pay-cli simulate ORDER_ID --event REFUND_COMPLETED
Creation returns HTTP 201 and a merchant refund entity with status SUBMITTED,
while the order and webhook report PENDING. A repeated create returns HTTP 200
with the existing refund, preserving its seller and amount and emitting no new
webhook. Completing with simulate changes the entity to REFUNDED, mirrors
COMPLETED onto the order, and emits REFUND_COMPLETED. The deposit fixes the
amount; a different --amount is rejected. Creating after completion returns 409.
refunds cancel ORDER_ID cancels only a pending local deposit, returns a
CANCELLED entity, and clears the order refund fields so a new refund can be
created. Cancellation emits no webhook, matching hosted behavior. Cancelling a
completed or already cancelled refund returns 404 Refund not found.
refunds get keeps returning the latest entity, including cancelled refunds.
Refund events created through simulate without a merchant refund keep their
existing behavior.
refunds get
Read an order refund using the owner's management session, or the local API key.
peer-pay-cli refunds get ORDER_ID [options]
Example:
peer-pay-cli refunds get ORDER_ID --profile live
refunds create
Create a refund using the owner's management session, or simulate a deposit locally.
peer-pay-cli refunds create ORDER_ID [options]
Options: --seller-username.
--seller-username is required, trimmed, and must contain 1 to 256 characters.
An order created before the merchant account changed owners cannot be refunded from the new owner's
account: hosted and local creation return HTTP 409 ORDER_FROM_PREVIOUS_OWNER. A refund
created before the transfer keeps its original wallet for retry and cancel.
Example:
peer-pay-cli refunds create ORDER_ID --profile live --seller-username seller123
refunds cancel
Cancel a pending order refund using the owner's management session, or the local API key.
peer-pay-cli refunds cancel ORDER_ID [options]
Example:
peer-pay-cli refunds cancel ORDER_ID --profile live
payments bridge
Read bridge status and attempt history using the management session, or the local API key.
peer-pay-cli payments bridge PAYMENT_ID [options]
Example:
peer-pay-cli payments bridge PAYMENT_ID
sandbox crypto-simulate
Simulate a crypto outcome through the hosted sandbox API, also served locally. Public endpoint; no API key or order token is required. For local mock event injection, use simulate.
peer-pay-cli sandbox crypto-simulate ORDER_ID [options]
Options: --outcome, --payment-id, --deposit-amount.
--outcome is required: success or failure. --payment-id optionally pins the current payment. --deposit-amount is a positive USDC decimal with at most 6 decimal places, valid only for a successful Apple Pay payment.
Example:
peer-pay-cli sandbox crypto-simulate ORDER_ID --profile live-sandbox --outcome success --payment-id PAYMENT_ID --deposit-amount 5
sandbox fail-payment
Fail a payment through the hosted sandbox API, also served locally. Public endpoint; no API key or order token is required. For local mock event injection, use simulate.
peer-pay-cli sandbox fail-payment ORDER_ID [options]
Options: --payment-id.
--payment-id is required.
Example:
peer-pay-cli sandbox fail-payment ORDER_ID --profile live-sandbox --payment-id PAYMENT_ID
sandbox expire-payment
Expire a payment through the hosted sandbox API, also served locally. Public endpoint; no API key or order token is required. For local mock event injection, use simulate.
peer-pay-cli sandbox expire-payment ORDER_ID [options]
Options: --payment-id.
--payment-id is required.
Example:
peer-pay-cli sandbox expire-payment ORDER_ID --profile live-sandbox --payment-id PAYMENT_ID
fees settlement-amounts
Calculate settlement and order contribution amounts. Public API; no API key or management session is required. Also served locally.
peer-pay-cli fees settlement-amounts [options]
Options: --fee-payer, --buyer-fee-share-bps, --quoted-bridge-spread-bps, --net-settled-usdc, --total-usdc-fee, --payment-amount, --currency-per-usd-rate.
--fee-payer, --net-settled-usdc, --total-usdc-fee, --payment-amount, and --currency-per-usd-rate are required. --fee-payer is MERCHANT, PAYEE, or SPLIT. SPLIT also requires --buyer-fee-share-bps, an integer from 0 to 10000 in steps of 1000. Amounts and rates are decimal strings: net USDC received, total USDC fees, fiat paid, and local currency per USD. MERCHANT contribution is fiat paid divided by the rate; PAYEE contribution is net USDC; SPLIT contribution is (1 - s) * (fiat paid / rate) + s * net USDC, where s = buyerFeeShareBps / 10000. If a payout bridge was quoted, pass --quoted-bridge-spread-bps as a nonnegative decimal string; the calculation first adjusts net USDC by 1 / (1 + quotedBridgeSpreadBps / 10000) to include that quoted cost. Amounts truncate to 6 decimals. The example returns net 97.5, fee 2.5, and contribution 100. A saved profile selects the API origin but needs no credentials.
Example:
peer-pay-cli fees settlement-amounts --fee-payer MERCHANT --net-settled-usdc 97.5 --total-usdc-fee 2.5 --payment-amount 92 --currency-per-usd-rate 0.92
# A $105 payment and $95 net settlement credit $100 of principal at a 50:50 split.
peer-pay-cli fees settlement-amounts --fee-payer SPLIT --buyer-fee-share-bps 5000 --net-settled-usdc 95 --total-usdc-fee 10 --payment-amount 105 --currency-per-usd-rate 1
Intentional local command restrictions
The help-command route smoke matrix covers every command's primary route.
Commands without HTTP routes (serve, profiles, logout, handoff, use,
and credentials export) operate on the local process or filesystem.
merchant referral: 403REFERRAL_SANDBOX_FORBIDDEN.payments recreate,payments extend,payments fulfill-sar: 403SANDBOX_NOT_SUPPORTED.merchant tier: 403TIER_CHANGE_SANDBOX_FORBIDDEN.merchant concierge: 403CONCIERGE_REQUEST_SANDBOX_FORBIDDEN.actions get: 404Action not found, because sandbox remediation creates no actions.login --profile local: rejected by the CLI; the direct localPOST /api/v1/auth/loginroute returns a 200 envelope with the merchant profile selected from the local session identity's memberships, falling back to the oldest membership whenX-Merchant-Idis absent or inaccessible.merchant switchandsandbox enable: hosted-profile operations;serveseeds one local merchant and saves only its API key in thelocalprofile.- Local payout commands only run for the seeded local merchant. Any other merchant (including
a sub-merchant or
X-Merchant-Idselection) gets 403PAYOUT_LOCAL_MERCHANT_ONLY. payouts create: the seeded merchant starts withsimulatedLiveon, so it may create payouts; with it off, create returns the hosted 403PAYOUT_SANDBOX_UNSUPPORTED. Only stablecoin and BTC funding is priced: BTC at a fixed $100,000 per BTC with the volatile buffer; any other funding token returns 422PAYOUT_ROUTE_UNQUOTABLE. There is no Relay quote, so 422PAYOUT_FUNDING_AMOUNT_TOO_LOWcan't occur locally. Amounts use the no-Relay-cost maths, the merchant wallet check is skipped, and the funding deadline is the API's fixed 24 hours (local settingsquoteTtlSecondsdoes not affect payouts), and the/c/checkout page is not served locally; its customer API is.payouts cancel: there is no Relay deposit check, so 409PAYOUT_FUNDING_DETECTEDcannot occur locally.payouts configure-local: stands in for the dashboard's settings routes, which need a dashboard sign-in; payouts start with every platform on.payouts simulate-paymentandpayouts simulate-peer-withdraw: there is no indexer or escrow; each step records the deposit change and runs the settlement refresh an indexer update would trigger. The local escrow stand-in's dust threshold is fixed at 0.10 USDC. The hosted API reads no threshold; it settles from the escrow'sDustCollectedevent.payouts simulate-fundingandpayouts sweep-local: funding is simulated. There are no Relay or chain reads; each simulated record stands in for one Relay request and its Base receipt, andsweep-local --nowstands in for the clock the funding sweeper runs on. Hosted profiles reject both commands before any request. Each simulated record carries a Relay request ID, so--superseded-bycan link records as Relay does.payouts simulate-payout-quoteandpayouts simulate-payout-bridge: no Relay or NEAR calls. Relay estimates use 1 USD per token. NEAR uses fixed fixtures: 1 ZEC = 100 USD, a 1.58 USDC minimum, a 20-minute refund deadline,sendBy5 minutes before it, and an address live for 10 more minutes.local settingsratesdoes not change these fixtures. Bridges move only on simulated evidence;sweep-localnever advances them.- Customer checkout (
/api/v1/cashout-checkout): the API's own router, link-token auth and controller run over local state.GET /api/v1/cashout-checkout/:idreturns the local merchant'smerchantCheckoutThemeasmerchant.checkoutThemeand itsdisableBrandingasmerchant.disableBranding, as the hosted API reads the merchant's current config. The Peer-app login stands in asAuthorization: Bearer local-player:<email>; any other token returns 401CASHOUT_LOGIN_INVALID, and another email returns 403CASHOUT_PLAYER_MISMATCH. An unknown payout id returns the same 401CASHOUT_TOKEN_INVALIDas a wrong token. Curator credentials, the escrow deposit and its events, the indexer updates and the wallet balance are thepayouts simulate-*records. See Local customer checkout for every route and both auth headers. The activity route's response includes the current payout's full recipientemailand acheckoutUrlon every item, using the same deterministic checkout token as payout creation. - Pay's sends from the customer wallet: there is no Privy signer, dispatcher or Base. The local
signer (
local-cashout-signer, policylocal-cashout-policy) is always attached and there is no attach step, so 409CASHOUT_SIGNER_NOT_ATTACHEDnever occurs locally. Queued sends wait untilpayouts resolve-sendconfirms or fails them; a fiat confirm always queues the USDC approve, and the calldata is a local stand-in. A state file saved before server-side sends loads a pending listing or USDC transfer that never reported a transaction hash asFAILED, so the customer can confirm again. One whose hash was reported loads unchanged, since its transaction may have gone out.
Other commands require their normal inputs and state (for example a fulfilled order before creating a refund).
use
Set the active profile. The name must already exist; local is allowed.
peer-pay-cli use NAME
peer-pay-cli use local
profiles marks it with active: true. Logging out of the active profile clears that selection.
status includes profile and mode in its response object.
sandbox enable
In a terminal, this command offers sign-in if the profile has no hosted management
session. After activating NAME-sandbox, run peer-pay-cli use NAME to return to
the hosted profile for merchant management or setup. Non-interactive setup errors
include this recovery command.
Enable the remote sandbox using the hosted management session, read its key from
the sandbox merchant profile, save <profile>-sandbox, and make it active. This
works for every integration path, including an unset path, and requires an OWNER
or MANAGER role. If the name belongs to a different API URL or sandbox merchant ID
(including a profile with no merchant ID), the CLI tries <profile>-sandbox-2,
-3, and so on until it finds an unused name or a matching profile. Re-running
with --profile NAME reuses only a matching API URL and sandbox merchant ID,
preserving its session, cached order tokens, and webhook secrets even when its
API key rotates. Setup follows the same naming rule and returns the chosen
sandboxProfile. A missing key
fails without changing the saved profile or active selection.
peer-pay-cli sandbox enable
The result includes sandboxProfile and activeProfile. To return to your merchant
account, run peer-pay-cli use NAME with the original profile name.
Seller disputes
disputes list
List seller disputes using the owner's management session, or the local mock.
peer-pay-cli disputes list [options]
Options: --status (comma-separated OPEN,ESCALATED or PAID), --order-id,
--page (default 1), --limit (1–100, default 20). --order-id filters one order's disputes.
Amounts in responses are raw 6-decimal USDC units.
Payment attempts are dashboard-only: the USDC transfer is signed by the merchant's Peer Pay wallet in the browser.
Example:
peer-pay-cli disputes list --status OPEN,ESCALATED
disputes get
Read one seller dispute using the owner's management session, or the local mock.
peer-pay-cli disputes get DISPUTE_ID [options]
Options: global profile and output options only. Payment attempts are dashboard-only: the USDC transfer is signed by the merchant's Peer Pay wallet in the browser.
Example:
peer-pay-cli disputes get DISPUTE_ID --profile live