Home/Developers/API Reference
DevelopersAPI Reference

Stablecoin Payment API. One endpoint. Five chains.

REST + JSON over HTTPS. Bearer-token authentication. One POST creates a payment link valid across USDC and USDT on Ethereum, Base, Polygon, Arbitrum, and BNB Chain.

//The modelone create, two patterns01/08

One create endpoint. Two integration patterns.

POST /v1/payments returns a paymentUrl. You can share it directly (payment-link pattern) or redirect / iframe it into your checkout (hosted-checkout pattern). The customer-facing flow is identical either way: only your distribution changes. There's no separate /checkout or /payment-links endpoint to wire up.

//Endpointsone to create · two auxiliary02/08

Integration is a single POST. GET and DELETE are auxiliary, used to retrieve current payment state or cancel an open one.

POST

/v1/payments

Create a payment. Returns a paymentUrl your customer can pay at, valid across the chains and tokens you accept.

Body
Required: amount (fiat string), currency (one of 14 fiat codes).
Returns
Optional: title, externalId, acceptedChains, acceptedTokens, expiresAt (15 min to 30 days, default 1 hour), sellers (for marketplace splits).
Notes
Returns: id, status=ACTIVE, paymentUrl, pymstrFee (1%), merchantFee, expiresAt, createdAt.
GET

/v1/payments/{id}

Retrieve a payment. Returns current state plus on-chain data once paid.

Body
Returns the merchant view: status, chainId, token, exchangeRate, amountToken, txHash, paidAt.
Returns
Use to poll for completion if you choose not to consume webhooks.
DELETE

/v1/payments/{id}

Cancel an ACTIVE payment. The customer-facing paymentUrl stops accepting transactions.

Body
Only valid when status is ACTIVE.
Returns
Returns the updated payment with status=CANCELLED.

The full surface

Beyond the payment lifecycle, the same Bearer key drives webhook configuration, API-key management, analytics, and the transaction ledger.

Payments
POST /v1/payments · GET /v1/payments/:id · GET /v1/payments · DELETE /v1/payments/:idCreate, retrieve, list (cursor-paginated), and cancel payments.
Webhook config
GET / POST / PUT /v1/webhooks · POST /v1/webhooks/regenerate-secret · POST /v1/webhooks/test · GET /v1/webhooks/deliveriesManage the endpoint, rotate the secret, send test events, inspect deliveries.
API keys
GET / POST /v1/auth/api-keys · DELETE /v1/auth/api-keys/:idCreate, list, and revoke pk_ keys. Optional IP whitelist, expiry from 30 days to never.
Analytics
GET /v1/analytics · GET /v1/analytics/exportPayment summary, splits by stablecoin and chain, 30-day CSV export.
Transactions
GET /v1/transactionsLedger records for every settlement, cursor-paginated.
Web3 proxy
POST /v1/web3/:chainIdJSON-RPC pass-through powering the checkout flow (eth_sendUserOperation).
//Create a paymentenforced rails · atomic split03/08

One POST. Full control of the rail.

acceptedChains and acceptedTokens enforce exactly which networks and stablecoins the customer can pay with, so wrong-chain payments are impossible by construction. sellers[] splits the payment across up to 5 wallets in the same on-chain transaction, with the 1% fee deducted proportionally from every recipient. The basic create is on the quickstart; this is the version marketplaces ship.

POST /v1/paymentsserver-side
// Enforced payment with a marketplace split: the customer
// can ONLY pay USDC on Arbitrum or Base, and one on-chain
// transaction pays all three wallets atomically.
const res = await fetch('https://api.pymstr.com/v1/payments', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.PYMSTR_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: '250.00',
    currency: 'USD',
    title: 'Premium Plan',
    externalId: 'sub-456',           // your reference. Unique while active
    acceptedChains: [42161, 8453],   // Arbitrum + Base only
    acceptedTokens: ['USDC'],
    merchantAmount: '180.00',
    sellers: [
      { address: '0x1234...abcd', amount: '60.00' },
      { address: '0x5678...efgh', amount: '10.00' },
    ],
  }),
});

const payment = await res.json();
// payment.paymentUrl -> share it, redirect to it, or iframe it
201 Createdresponse
{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "status": "ACTIVE",
  "paymentUrl": "https://pay.pymstr.com/p/01234567-...",
  "pymstrFee": "2.50",
  "merchantFee": "178.20",
  "sellers": [
    { "address": "0x1234...abcd", "amount": "59.40" },
    { "address": "0x5678...efgh", "amount": "9.90" }
  ],
  "expiresAt": "2026-03-01T00:00:00.000Z",
  "createdAt": "2026-02-24T10:00:00.000Z"
}
//AuthenticationBearer token · pk_ prefix04/08

One header. Bearer token.

Every API call carries an Authorization header with your secret API key. Keys are created in the PYMSTR dashboard and prefixed with pk_.

Use pk_ keys server-side only. Never ship a key in a browser bundle. See docs.pymstr.com for full authentication details, including session-token usage and security features.

Authorization headerexample
Authorization: Bearer pk_live_abc123...xyz

# Test the connection:
curl https://api.pymstr.com/v1/payments \
  -H "Authorization: Bearer pk_live_..." \
  -X GET
//Payment states6 possible values05/08

Six states. One forward direction.

ACTIVEPayment created, waiting for the customer
PROCESSINGCustomer selected a chain/token and submitted the transaction
SUCCESSFULOn-chain transaction confirmed
FAILEDTransaction reverted or bundler error
EXPIREDPayment reached its expiresAt without completing
CANCELLEDMerchant cancelled the payment via DELETE /v1/payments/{id}
//Safe retriesexternalId dedupe · 409 on duplicate06/08

Retries that can't double-charge.

Pass your own reference (an order ID, a subscription ID) as externalId. It must be unique per merchant while a payment is active or successful, so a retried create call can never mint a second live payment: the API answers 409 Conflict instead. Once a payment fails, expires, or is cancelled, the externalId becomes reusable.

List endpoints paginate with a cursor: pass the last item's id as cursor and read nextCursor + hasMore from the response (limit defaults to 20, max 100). Validation failures return a structured errors array, so a 400 tells you exactly which field to fix.

Error shapesreference
// Duplicate externalId while a payment is live:
HTTP/1.1 409 Conflict
{
  "statusCode": 409,
  "message": "externalId already in use",
  "error": "Conflict"
}

// Validation failure shape:
HTTP/1.1 400 Bad Request
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request",
  "errors": [
    { "path": ["amount"], "message": "Invalid amount format" }
  ]
}
//Limits + coveragerate · chains · currencies07/08

Rate limits

Every response includes rate-limit headers. When you exceed the limit, the API returns 429 Too Many Requests.

  • X-RateLimit-LimitMaximum requests allowed in the window
  • X-RateLimit-RemainingRequests remaining in the current window
  • X-RateLimit-ResetUnix timestamp when the window resets

Current limits per endpoint are documented at docs.pymstr.com.

Chains + tokens

  • Ethereum id=1USDC, USDT
  • Arbitrum One id=42161USDC, USDT
  • Base id=8453USDC
  • Polygon id=137USDC, USDT
  • BNB Smart Chain id=56USDT

14 fiat currencies for the amount field: USD · EUR · GBP · AED · ARS · BRL · IDR · ILS · JPY · MXN · NGN · PHP · THB · VND.

//API Reference questions5 answers08/08
A REST API that lets your backend create and track payments settled in stablecoins (USDC and USDT) instead of card rails. With PYMSTR, one POST to /v1/payments returns a hosted paymentUrl that accepts USDC or USDT across Ethereum, Base, Polygon, Arbitrum, and BNB Chain. Settlement is on-chain, direct to your wallet, with a 1% flat fee and no chargebacks. You integrate two things: the create call and the payment.completed webhook.
One POST to /v1/payments with an amount and one of 14 fiat currencies. The response includes a paymentUrl you can share, redirect to, or embed. Optional fields give you control: acceptedChains and acceptedTokens enforce exactly which networks and stablecoins the customer can pay with, expiresAt bounds the payment window (15 minutes to 30 days), and sellers[] splits the payment across up to 5 wallets in a single on-chain transaction.
Use the externalId field. It is your own reference (an order ID, a subscription ID) and must be unique per merchant while a payment is active or successful, so retrying a create call with the same externalId cannot produce a duplicate live payment: the API returns 409 Conflict instead. After a payment fails, expires, or is cancelled, the externalId becomes reusable. There is no separate idempotency-key header; externalId is the deduplication mechanism.
USDC and USDT across 5 chains: Ethereum (1), Polygon (137), Arbitrum (42161), Base (8453), and BNB Chain (56). Base is USDC-only and BNB Chain is USDT-only; the other three support both. Pass chain IDs in acceptedChains and token symbols in acceptedTokens to restrict a payment, or omit both to accept everything.
Bearer authentication with a secret key prefixed pk_, created in the dashboard. Keys can be restricted to specific IP addresses (up to 20) and set to expire after 30, 90, or 180 days, 1 year, or never. You can hold up to 10 active keys, which makes zero-downtime rotation straightforward: create the new key, deploy it, then revoke the old one. Keys are server-side only; never ship one in a browser bundle.

Build with the stablecoin rail.