Agent quickstart — Element Pay Partner API
Short path for coding agents and humans. Contract:../openapi.yaml. Standard local fiat ↔ USDC/USDT: integration-fiat-stablecoin.md. Gaps: KNOWN_GAPS.md.
Install / auth
No SDK required. Call HTTPS JSON from your backend.webhook_url). See sandbox/onboarding.mdx.
Security scheme in OpenAPI: APIKeyHeader (X-API-Key). Webhook HMAC uses webhook_secret on the key — not request auth.
Sandbox credentials / test amounts
Never send sandbox phones/
1111111111 accounts on production. Full matrix: sandbox/success-failure.mdx.
Happy path — local fiat → USDC (OnRamp)
Same endpoints for every corridor: discover → quote → accept →order.settled. Only country / currency / network_id / amount / asset change. OffRamp (crypto → local) is documented in integration-fiat-stablecoin.md.
Worked example below: KE M-PESA OnRamp → Base USDC (fund portal: credit ledger on settle).
Numbered steps (copy-pasteable)
0. Envcustomer_id (pcus_*) with status=approved before quote. Full curls: customers/quickstart.mdx. Minimal flow:
GET /partner/customers/requirementsPOST /partner/customers→ savepcus_*POST /partner/customers/{customer_id}/documents(identity + address)POST /partner/customers/{customer_id}/submit- Wait for
customer.approvedwebhook or pollGET /partner/customers/{customer_id}untilstatusisapproved
network_id (do not hardcode across envs)
POST /partner/orders/quote
POST /partner/orders/{quote_id}/accept)
order.settled; backup poll:
amount_crypto (API returns decimal stablecoin; store as integer minor units × 10⁶ if that is your ledger convention). Details: integration-fiat-stablecoin.md.
Webhook verify + idempotency
Headers (every delivery):webhooks.mdx):
- Parse
t=…,v1=… - Reject if
tolder than 5 minutes - HMAC-SHA256 over
{t}.{raw_body}withYOUR_WEBHOOK_SECRET; constant-time compare tov1 - Dedupe on
X-Webhook-Id; return 2xx quickly
order.processing → order.settled | order.failed | order.refunded.
Idempotency elsewhere
Corridor quotes do not take an
idempotency_key on quote/accept.
Banking (optional) — customers + accounts
Corridor ramps need vault KYC only (status=approved). Accounts are a separate product: EUR/USD IBAN, stablecoin ledger, deposit instructions, sends, book transfers, conversions, payouts. Full map: customers/route-cheat-sheet.mdx · guide: customers/accounts.mdx.
approved alone does not unlock accounts. Calling account routes before ready → 409 (Deposit account is not ready). Prefer webhook customer.deposit_account.updated; poll GET /partner/customers/{customer_id} as backup.
Sequence
- Wait until
products.deposit_account.statusisready POST /partner/customers/{customer_id}/accounts(open fiat or stablecoin rail; idempotent on rail tuple)GET /partner/customers/{customer_id}/accounts→ saveaccount_idGET …/accounts/{account_id}/deposit-instructionswhen you need bank details or a deposit wallet- Money movement (each has its own guide; confirm uses
idempotency_key):- Stablecoin sends:
…/sends/preview→…/sends - Book transfers (same currency):
…/book-transfers/preview→…/book-transfers - Conversions (cross-currency, same
pcus_*):…/conversions/preview→…/conversions - External bank payouts:
…/payout-methods→…/payout-banks→…/payouts/preview→…/payouts
- Stablecoin sends:
Open examples (sandbox)
- Sandbox outbound sends/conversions/payouts may need an Element Pay sandbox credit (
customers/deposit-instructions.mdx). - USD fiat open is not available for Kenya individuals (
unsupported_region) — seecustomers/accounts.mdx. - Card routes are documented in
customers/cards.mdxbut not inopenapi.yamlyet (docs/KNOWN_GAPS.md).
account.opened, account.ready, account.credited, account.send.completed, account.send.failed.
Common pitfalls
- Quoting before vault
status=approved→422 - Calling accounts before
deposit_account.status=ready→ 409 - Hardcoding
network_idfrom docs instead of your catalog - Using sandbox success phones on production
- Treating
GET /partner/rates/indicativeas the binding FX (it is not; use quoteamounts.rate) - Confusing corridor OnRamp with ledger
…/conversions(fiat↔fiat on banking accounts only) - Crediting the user ledger on accept/
order.processinginstead oforder.settled - Missing webhook signature check or clock skew / replay beyond 5 minutes
- Reusing OffRamp crypto deposit addresses across orders (per-order in production)
OpenAPI path map
Ramp happy path
Banking (after deposit_account.status=ready)
All listed under
paths in openapi.yaml (except cards — see KNOWN_GAPS.md).