API reference
Eleven integration endpoints, all POST with a JSON body, plus a GET liveness probe. Nothing is authenticated in v1: the capability in the body is what authorizes the call.
Authentication
There is no API authentication in v1. Payments are secured by the customer’s signature and by the contract, not by knowing who is calling. There are no API keys, no bearer tokens and no accounts.
What protects each call instead is the capability it carries. Payment intents, setup tokens, subscription capabilities and cancel tokens are HMAC-signed by the deployment and verified on every request; a token minted for another deployment fails here even if a key were shared. The rate limits below protect the service itself.
A p2s2 subscription capability authorizes charges against a customer’s wallet. Keep it server-side. Use /v1/subscriptions/revoke/session when something has to reach a browser.
Capabilities never appear in a path or query string, which is what a server, a proxy and an access log actually record. The hosted checkout does carry one in the URL fragment — browsers do not send a fragment in the request or in the Referer header, so it stays on the client. That protects it from your infrastructure, not from everything running in the page.
Conventions
- The eleven integration endpoints are all POST with a JSON body — including the reads, so a capability travels in the body rather than in a path or query string a server would log. GET /health is the one exception: a liveness probe that takes no input.
- Unknown fields are rejected, not ignored. A field the API does not recognise means the caller expects something it is not honouring.
- Amounts are decimal strings ("100.00"); base units appear alongside them as integer strings.
- Errors return { "error": CODE }, usually with an action telling your system what to do. See Errors.
- CORS is open and credentials are never allowed: there is no cookie or header authority for another origin to borrow.
- Request bodies are capped at 16 KB.
Payments
Mint a one-time payment intent.
Returns intent, reference, amount, expires_at, and a pay object with chain_id, splitter, token, recipient, amount_units and reference.
Authoritative terms for a checkout to display.
Returns the recipient, amount, base units, token, splitter, chain id, reference and expiry. Rejects a payment the contract has already settled.
Server-side proof that a payment landed.
Returns HTTP 200 either way. A proven payment returns valid: true with tx_hash, reference, amount, block_number and block_hash. Anything else returns { valid: false, code } — including PAYMENT_CONFIRMING, which is not a 409 on this endpoint. Branch on valid. Only transport failures use status codes: 400 for a malformed body, 429 for rate limiting, 500 for an unexpected fault.
Subscriptions
Create the technical terms of a subscription.
Returns setup_token, expires_at, chain_id, contract, amount and salt.
Terms plus the EIP-712 scaffold for a custom checkout.
Returns the full terms, max_gas_reimbursement, fee_bps, network_fee, typed_data, and a best-effort network_fee_estimate which may be null.
Exchange the customer’s signature for a capability.
Returns subscription (the p2s2 capability), subscription_id, amount, period and end.
Current state, read from the chain.
Returns the state fields documented in Recurring payments, including the echoed terms.
Mint a short-lived cancel token safe for a browser.
Returns cancel_token, expires_at (15 minutes by default), subscription_id and payer.
Unsigned calldata that cancels one subscription.
Returns chain_id, payer, to, data, subscription_id and a human-readable description. Only the payer’s wallet can send it.
Unsigned calldata that removes the token allowance entirely.
Returns chain_id, to, data and description. Stops every P2Flux subscription paid in this token from that wallet.
Charges
Attempt one recurring charge. Idempotent per billing period.
Returns status, ok, already_paid, action, subscription_id, period_index, period_start, period_end, next_period_at, and — when a transaction was sent — tx_hash and amount.
Service
Liveness only. Returns { "ok": true } and never grows a body.
/metrics and /ready exist but are bound to a loopback-only listener and are not reachable from outside the host. They are operational, not part of the integration surface.
Rate limits
Per minute, per client IP unless noted. These are a ceiling on abuse, not a merchant quota — there are no accounts, and many merchant sites share one outbound address. Defaults shown; an operator can change them.
Exceeding one returns RATE_LIMITED or CONCURRENCY_LIMIT with HTTP 429, a retry_after field and a matching Retry-After header. Nothing was charged.
There is no idempotency header, because the two write paths do not need one: a payment is settled once by the contract’s replay guard, and a charge is allowed once per billing period.