PFlux DOCS
GitHub Integration enquiries Start integrating
REFERENCE/API REFERENCE

API reference

21 integration endpoints, all POST with a JSON body, plus a GET liveness probe and a GET alias for capabilities. Nothing is authenticated in v1: the capability in the body is what authorizes the call.

BASE URLapi.p2flux.com
TRANSPORTHTTPS · JSON
METHODPOST
AUTHNone in v1
INTERACTIVE

Try these requests in the API explorer — the same endpoints, browsable, against the Base Sepolia test environment. Both pages come from openapi.json, the contract the API is tested against on every change.

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.

TREAT CAPABILITIES AS SECRETS

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 21 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. Two GETs sit outside that: GET /health, a liveness probe that takes no input, and GET /v1/capabilities, which answers the same body as its POST.
  • 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

POST/v1/payments

Mint a one-time payment intent.

BODY FIELDTYPEREQUIREDNOTES
recipientstringYesAddress that receives the payment.
amountstringYesDecimal USDC.

Returns intent, reference, amount, expires_at, and a pay object with chain_id, splitter, token, recipient, amount_units and reference.

POST/v1/payments/resolve

Authoritative terms for a checkout to display.

BODY FIELDTYPEREQUIREDNOTES
intentstringYesThe signed intent.

Returns the recipient, amount, base units, token, splitter, chain id, reference and expiry. Rejects a payment the contract has already settled.

POST/v1/payments/verify

Server-side proof that a payment landed.

BODY FIELDTYPEREQUIREDNOTES
intentstringYesThe signed intent.
tx_hashstringYes0x + 64 lowercase hex.

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. An expired intent is not a failure here: verification reads history and ignores expiry.

POST/v1/payments/recover

Find a settled payment when its transaction hash was lost.

BODY FIELDTYPEREQUIREDNOTES
intentstringYesThe intent of the payment being looked for. No hash.

Returns { found: true, … } with the ordinary verification result, { found: true, tx_hash, valid: false, code } when the transaction exists but does not verify yet, or { found: false, code: "PAYMENT_NOT_FOUND", as_of_block }. A miss is a statement about that block, never a verdict that nothing was paid. Runs at low priority and can be expensive — it is for a missing callback, not for polling.

Subscriptions

POST/v1/subscriptions

Create the technical terms of a subscription.

BODY FIELDTYPEREQUIREDNOTES
recipientstringYesAddress that receives each renewal.
amountstringYesDecimal USDC per period.
periodintegerYesSeconds between charges.
endintegerNoUnix seconds; 0 means no end.

Returns setup_token, expires_at, chain_id, contract, amount and salt.

POST/v1/subscriptions/resolve

Terms plus the EIP-712 scaffold for a custom checkout.

BODY FIELDTYPEREQUIREDNOTES
setup_tokenstringYesFrom /v1/subscriptions.

Returns the full terms, max_gas_reimbursement, fee_bps, network_fee, typed_data, and a best-effort network_fee_estimate which may be null.

POST/v1/subscriptions/finalize

Exchange the customer’s signature for a capability.

BODY FIELDTYPEREQUIREDNOTES
setup_tokenstringYes
payerstringYesThe signing wallet.
signaturestringYesEIP-712 signature.

Returns subscription (the p2s2 capability), subscription_id, amount, period and end.

POST/v1/subscriptions/status

Current state, read from the chain.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesThe capability.

Returns the state fields documented in Recurring payments, including the echoed terms.

POST/v1/subscriptions/revoke/session

Mint a short-lived cancel token safe for a browser.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesThe capability.

Returns cancel_token, expires_at (15 minutes by default), subscription_id and payer.

POST/v1/subscriptions/revoke/prepare

Unsigned calldata that cancels one subscription.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesA capability or a cancel token.

Returns chain_id, payer, to, data, subscription_id and a human-readable description. Only the payer’s wallet can send it.

POST/v1/allowances/revoke/prepare

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.

POST/v1/allowances/restore/session

A narrow session for restoring one subscription’s allowance.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesThe capability.

Returns approve_token (the p2approve1 session, 15 minutes), expires_at, payer and subscription_id. The token can approve and nothing else: it cannot charge, revoke, refund, or become a capability. Open <checkout>/#/approve/<approve_token>. See Restore an allowance.

POST/v1/allowances/restore/resolve

Read an allowance-restore session back, for the browser holding it.

BODY FIELDTYPEREQUIREDNOTES
approve_tokenstringYesFrom the session call.

Returns chain_id, token, spender (the recurring contract — never a caller-supplied address), payer, subscription_id, required_units (the signed amount plus the gas reimbursement the next charge may add) and expires_at.

Refunds

All three take the original settlement: tx_hash plus either intent for a one-time payment, or subscription and period_index for a renewal. Neither the payer, the merchant nor a maximum is ever accepted from the caller — all three are derived from the chain.

POST/v1/refunds/prepare

Lock the terms of a refund the merchant is about to send.

BODY FIELDTYPEREQUIREDNOTES
tx_hashstringYesThe transaction that settled the original payment.
amountstringYesBase units — micro-USDC integer. Must be positive and no larger than the original.
intent / subscription + period_indexstringYesWhichever identifies the settlement.

Returns refund_token (15 minutes, for the browser only — do not store it), chain_id, token, merchant, payer, original_amount, refund_amount, their base-unit twins, and expires_at.

POST/v1/refunds/resolve

Read the terms behind a prepare token.

BODY FIELDTYPEREQUIREDNOTES
refund_tokenstringYesFrom prepare.

Returns the same terms for the browser holding the token. Touches no chain and changes nothing.

POST/v1/refunds/verify

Confirm a refund happened and is settled.

BODY FIELDTYPEREQUIREDNOTES
tx_hashstringYesThe original settlement.
refund_amountstringYesBase units.
refund_tx_hashstringYesThe transfer the merchant sent.

Returns status: "REFUNDED" with refund_tx_hash, refund_amount, original_amount, payer, merchant and block_number. Takes the original settlement rather than the prepare token, so reconciliation works days later.

Charges

POST/v1/charges

Attempt one recurring charge. Idempotent per billing period.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesThe capability. No amount or recipient — both come from the signed permission.

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.

POST/v1/charges/recover

The transaction that charged one exact period, when the response was lost.

BODY FIELDTYPEREQUIREDNOTES
subscriptionstringYesThe capability.
period_indexintegerYesExact and required. Reconciliation is about one specific collection.
hintobjectNo{ attempted_at } or { block }. Narrows the search; never evidence.

Returns { found: true, subscription_id, period_index, tx_hash, block_number, payer, recipient, net_units, fee_units, network_fee_units, amount_units } when the contract’s own SubscriptionCharged log names this subscription and this period and matches the signed terms; { found: false, code: "PAYMENT_NOT_FOUND", as_of_block } otherwise — ordinary for a skipped period, and a statement about one block rather than a verdict. 409 PAYMENT_CONFIRMING (with the hash) below finality, 503 RECOVERY_UNAVAILABLE when the bounded search cannot finish, 502 PAYMENT_RECOVERY_INCONSISTENT for a log that contradicts the terms. Full semantics in Recover a lost charge.

Service

GET/health

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.

SCOPEDEFAULT
Global600
/v1/payments120
/v1/subscriptions120
Checkout calls (resolve, finalize)180
/v1/subscriptions/status (also per subscription), /v1/payments/verify, /v1/refunds/verify, /v1/refunds/resolve300
/v1/subscriptions/revoke/session and /v1/allowances/restore/session60
/v1/payments/recover and /v1/charges/recover per IP30
/v1/payments/recover per payment, /v1/charges/recover per subscription6
/v1/charges per IP300
/v1/charges per subscription6
Simultaneous charges in flight20
Sponsored transactions per buyer wallet unresolved on chain at once (network fee paid in USDC)1
Sponsored broadcasts per buyer wallet, emergency ceiling, any rolling hour / day (successes are not otherwise limited)250 / 1000
Failed sponsored simulations per buyer wallet, any rolling hour20
Reverted sponsored transactions per buyer wallet, any rolling day, before sponsorship is refused for that day2

Exceeding one returns RATE_LIMITED or CONCURRENCY_LIMIT with HTTP 429, a retry_after field and a matching Retry-After header. Nothing was charged.

NO IDEMPOTENCY KEYS

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.

Something more than a standard integration?
Marketplace flows, platform billing and custom settlement logic.
Discuss an integration