BitriPay
ONE QR. ONE GATEWAY. EVERY ELIGIBLE RAIL.Pay Local. Fund Global. Settle Your Way.

Three calls. One coffee.

Connect your shop, your billing system or your institution to BitriPay: create an intent, let the customer pay over the rail Smart Route picks, receive a signed webhook. A key-authenticated REST API with 116 documented operations, idempotent money movement and a sandbox that runs the same state machine as production.

1 · Get a key

Sign in → Developer portal → Create key

Secret keys (sk_) carry every scope, restricted keys (rk_) only the scopes you list, publishable keys (pk_) can only start a payment. The secret is shown once.

2 · Authenticate

Bearer on every request

Authorization: Bearer sk_test_… — test keys hit the sandbox, live keys the real rails, on the same base URL. Send Idempotency-Key on every money-moving POST.

3 · Call the engine

Intent → checkout → webhook

Create a payment intent, redirect to its checkout URL or show its BitriQR, then act on payment_intent.succeeded and payment_intent.settled after verifying the signatures.

# Base URL (test and live keys share it)
https://api.bitripay.com/v1

# 1. Create an intent
curl -X POST https://api.bitripay.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_…" -H "Idempotency-Key: order-1042" -H "Content-Type: application/json" \
  -d '{"amount_minor":250000,"currency":"CDF","description":"Order 1042","allowed_operators":["orange_cd","mpesa_cd","airtel_cd","africell_cd"]}'

# 2. Send the customer to checkout_url (or render qr_payload), then
# 3. Verify the webhook: BitriPay-Signature (HMAC of the endpoint secret) and the platform Ed25519 key from /v1/keys

Endpoints you will use first

MethodPathDescription
POST/payment_intentsCreate a payment intent (returns checkout_url, qr_payload and client_secret)
GET/payment_intentsList payment intents
GET/payment_intents/{id}Retrieve a payment intent
POST/payment_intents/{id}/cancelCancel an intent that has not been captured
POST/checkout_sessionsCreate a hosted checkout session with line items
GET/checkout_sessionsList checkout sessions
POST/payment_linksCreate a single-use or reusable payment link
GET/payment_linksList payment links
POST/qr_codesCreate a static BitriQR (EMVCo + signed extension)
GET/qr_codesList QR codes
POST/verificationsScan-to-Verify: did a payment reach me? (reference, or MSISDN + amount)
POST/refundsRefund a captured intent (atomic reservation; wallet or processor)
GET/refundsList refunds
POST/payoutsPay out to a bank account or mobile-money number
GET/payoutsList payouts
GET/balanceBalance classes: available, pending, reserved, settlement_pending, held, disputed, frozen
POST/webhook_endpointsCreate an endpoint (returns whsec_ secret once)
GET/keysPublic signing-key registry (ed25519, ETag cached)
GET/statusOperating state (guardian mode, degraded flags)

Every operation, every schema and every error code: OpenAPI 3.1 document · platform signing keys · operating status.

Scopes

A restricted key holds only the scopes you choose; a scope that is missing returns scope_denied.

ScopeGrants
wallets:readRead wallets.
transfers:readRead wallet transfers.
transfers:writeSend wallet transfers under the organisation policy.
remittances:readRead remittances.
remittances:writeQuote and send remittances on authorised corridors.
payouts:approveApprove a payout batch (four eyes: never the author).
ai:runRun metered assistant operations (consumes prepaid units).
subscriptions:readRead plans, subscriptions and invoices.
subscriptions:writeCreate plans, record usage, cancel subscriptions.
credit:readRead the credit-readiness signal a customer consented to share.
routes:readRead routes and receipts.
routes:writeQuote and create any-to-any routes.
payment_intents:readRead intents, timelines and refundable amounts.
payment_intents:writeCreate and cancel payment intents. Publishable pk_ keys get only this: safe to ship in a browser, they can start a payment and never read your data.
checkout_sessions:writeCreate hosted checkout sessions.
payment_links:writeCreate and deactivate payment links.
qr_codes:readList your QR codes and their analytics.
qr_codes:writeCreate and revoke QR codes and locations.
refunds:readRead refunds.
refunds:writeCreate refunds; every refund is reserved atomically against the refundable amount.
verifications:writeSubmit a customer reference or operator SMS for verification.
payouts:readRead payouts and batches.
payouts:writeCreate payouts and bulk batches (approval stays with a person).
balance:readRead balances by class (available, held, escrow, suspense).
webhooks:manageRegister endpoints, read deliveries, replay.
events:readRead the event feed.
payments:createInitiate national-switch payments.
payments:readRead national-switch payments.
payments:cancelCancel a pending national-switch payment.
refunds:createCreate national-switch refunds.
participants:readRead national-switch participants.
qr:createCreate switch-compatible QR codes.
reconciliation:readRead reconciliation reports and receipts.
bindings:manageManage account bindings.
settlements:readRead settlement profiles, cycles and statements.
settlements:writeManage settlement profiles and close cycles.
disputes:readRead disputes and chargebacks.
disputes:writeRespond to disputes with evidence.
accounts:readList and read your connected accounts: status, verification, balances.
accounts:writeCreate your customers' connected accounts, send claim links, set application fees, detach.
*Full account scope (sk_ keys).

Drop-in checkout: pay by mobile money, automatically

Add “Pay with BitriPay” to any website or marketplace. The customer picks the operator, pays to the collection number, and BitriPay matches the operator's confirmation; the order moves forward on its own. Two integration paths:

1 · Hosted checkout (recommended)

Create an intent or a checkout session and redirect to its URL. BitriPay renders the operators, the reference, the QR and the confirmation step, then returns the customer to your success URL and sends the webhook.

2 · Embedded widget

Load the checkout script with a publishable key and mount the panel in your page; the browser can start a payment but never read your data. WooCommerce and Shopify plugins ship in the repository.

Platforms and aggregators: onboard your customers by API

Marketplaces, SaaS, billing systems, cooperatives, schools, institutions: create each customer's merchant account with one call, take their payments with your own key, keep your fee, and hand the account over when they are ready. BitriPay holds the aggregator licence and remains the regulated party; each customer is the merchant of record with its own wallets, settlement profile, statements and verification.

# 1. Create the customer's account (they get a claim link to own it)
curl -X POST https://api.bitripay.com/v1/accounts -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json"   -d '{"business_name":"Pharmacie Lumière","type":"merchant","email":"owner@pharmacie.example","country":"CD","application_fee_bps":150}'
# → {"id":"acct_…","status":"active","kyb_status":"none","onboarding":{"claimed":false}, …}

# 2. Take a payment for that customer with YOUR key: add the account header (works on every operation)
curl -X POST https://api.bitripay.com/v1/payment_intents -H "Authorization: Bearer sk_live_…" -H "BitriPay-Account: acct_…"   -H "Idempotency-Key: inv-88" -H "Content-Type: application/json"   -d '{"amount_minor":250000,"currency":"CDF","description":"Invoice 88","application_fee_minor":3750}'

# 3. Their events reach your webhooks with "account":"acct_…"; send them a claim link when they should sign in themselves
curl -X POST https://api.bitripay.com/v1/accounts/acct_…/account_links -H "Authorization: Bearer sk_live_…"

Merchant of record

The customer's money lands in the customer's wallet and settles by the customer's settlement profile; your application fee is a transparent split on the intent, paid at capture and shown on both statements.

Every operation, one header

Intents, checkout sessions, payment links, QR codes, refunds, payouts, balance, disputes, settlement: whatever your key may do for you, it does for a connected account with BitriPay-Account. Scopes still apply.

Hand-over and revocation

The customer claims the account with a one-time link and sees you under Team as administrator. They can remove you at any time, or you detach: keys, wallets and history stay theirs.

Install BitriPay for your clients

BitriPay holds the aggregator licence; you integrate it into your clients' websites, apps, billing and institutional systems. Each client is the merchant of record: their money settles to their own account, their statements are theirs, and you never hold their funds or their password.

1 · The client opens a merchant account

Or you open it with their details

Every business, NGO, institution or government body you integrate registers as a merchant-class account and passes business verification as its volume grows.

2 · The client adds you to its team

Command centre → Team → developer role

You sign in with your own credentials and act for the client with the developer role: API keys, webhooks and payment creation — nothing on settlement, payouts or customer exports. Every action is written to the client's audit trail under your name.

3 · Integrate and hand over

Keys per client, sandbox first

Pick the client in the workspace selector, create its test key, integrate with the hosted checkout, the embedded widget, the WooCommerce or Shopify plugin or an SDK, then switch to a live key. The client can remove you at any time; its keys stay its own.

Sandbox magic numbers

With a test key, these MSISDNs drive an intent through the real attempt machine so you can test every outcome without an operator.

NumberOutcomeWhat happens
+243000000501succeedCustomer approves → captured, settled to the test balance, payment_intent.succeeded webhook
+243000000404failWallet not found → attempt fails (invalid_msisdn), intent returns to REQUIRES_PAYMENT_METHOD
+243000000408ambiguousProvider outcome unknown → payment parked in MANUAL_REVIEW, intent AMBIGUOUS, payment_intent.ambiguous_hold webhook
+243000000500timeout_then_succeedTimeout then success → pending for 6 seconds, then succeeds and settles
+243000000503provider_unavailableProvider unavailable → retryable failure (provider_unavailable)
any number ending in 0000declinedCustomer rejects the prompt → declined

Everything else in the catalogue

MethodPathDescription
GET/payment_intents/{id}/timelineEvery state change and attempt of an intent
GET/payment_intents/{id}/methodsPayment methods available to the payer for this intent
GET/payment_intents/{id}/refundableAmount still refundable on an intent
GET/payment_intents/{id}/splitsSplit payouts made from a captured intent
GET/checkout_sessions/{id}Retrieve a checkout session
POST/checkout_sessions/{id}/expireExpire an open session
POST/payment_links/{id}/deactivateDeactivate a link
GET/qr_codes/analyticsScan analytics
POST/qr_codes/{id}/revokeRevoke a code (lost, stolen, tampered, replaced, retired)
POST/resolveResolve any scanned payload (public; trust from the key registry)
POST/locationsCreate a merchant location
POST/locations/{id}/terminalsAdd a terminal to a location
GET/refunds/{id}Retrieve a refund
GET/verifications/quotaFree quota and price
GET/walletsWallet balances with the available amount after holds
POST/transfers/quoteQuote a transfer with the full fee and FX disclosure, plus rails ranked by the smart router
POST/transfersSend from the wallet to another BitriPay account
GET/transfers/{id}Retrieve a transfer
POST/routes/quoteQuote any → any: fund from the wallet or any card / bank / mobile money, deliver to a wallet, QR, bank account, mobile money number or agent anywhere, in the currency the recipient will receive; rate, margin and every fee disclosed
POST/routes/payout-currenciesWhich currencies the recipient can receive for a destination right now (corridor rules, licence, liquidity)
POST/routesCreate a route: the funding leg runs through the processor (card / bank / mobile money) or the wallet, the payout leg follows automatically once funds are confirmed
GET/routesList routes
GET/routes/{id}Retrieve a route with its stage, quote, funding payment and payout
GET/routes/{id}/receiptReceipt for a settled route
POST/routes/{id}/cancelCancel before the payout is executed (funds return; card funding refunded where the processor allows)
GET/remittances/quoteQuote an international transfer
POST/remittancesSend an international transfer to a wallet, bank account, mobile money number or cash pickup in the recipient currency; add a `source` (any card, bank or mobile money) to fund it through a processor instead of the wallet
GET/remittancesList remittances
GET/payouts/batches/columnsCSV columns accepted for a batch upload
POST/payouts/batchesUpload a payout batch (rows or CSV); every row is validated and the totals are returned before approval
GET/payouts/batchesList payout batches
GET/payouts/batches/{id}Retrieve a batch with its rows and readiness (funds, invalid rows, approval rule)
POST/payouts/batches/{id}/approveApprove and execute a batch: four-eyes by another account holder, or the creator under PIN / passkey step-up
POST/payouts/batches/{id}/cancelCancel a batch awaiting approval
POST/ai/{agent}Run an agent (canonical names such as RouteOptimiser, FraudScorer, SavingsAdvisor accepted); metered in ACU, provider and model never disclosed
GET/ai/runs/{id}Retrieve an agent run
GET/plansList your subscription plans
POST/plansCreate a plan (interval, trial, tax, metered usage)
GET/plans/{id}Retrieve a plan
POST/plans/{id}/archiveStop offering a plan (existing subscriptions continue)
GET/subscriptionsList subscriptions with a billing overview (recurring revenue, collections, dunning)
GET/subscriptions/{id}Retrieve a subscription with its invoices
POST/subscriptions/{id}/usageRecord metered usage for the current period
POST/subscriptions/{id}/cancelCancel at period end (or immediately)
GET/invoicesList invoices
GET/credit_readiness/{code}Read a consented credit-readiness signal (score, band, factors); never the transactions, never a lending decision
GET/settlement_profilesSettlement profiles per rail and currency
POST/settlement_profilesCreate or update a profile (T0/T1/T2/weekly/manual, cut-off, destination, minimum)
GET/settlement_calendarUpcoming cut-offs, obligations and recent cycles
GET/settlement_cyclesList cycles
POST/settlement_cyclesClose the running period now (optionally pay)
GET/settlement_cycles/{id}/statementNumbered, hashed statement (format=json|csv|pdf)
POST/settlement_cycles/{id}/payPay a closed cycle to its destination
GET/disputesDisputes against my payments
POST/disputesOpen a dispute on a payment I received
POST/disputes/{id}/respondRespond with evidence before the deadline
GET/webhook_endpoints/{id}/deliveriesDeliveries with attempts and status
POST/webhook_deliveries/{id}/replayReplay a delivery
GET/eventsEvent log
POST/events/{id}/replayReplay an event to every endpoint
GET/api_keysList API keys (session only; keys cannot mint keys)
POST/api_keysCreate sk_ / rk_ / pk_ key with scopes
GET/sandboxMagic MSISDNs and simulated outcomes
POST/offline/devicesRegister a device offline subkey (ed25519 SPKI, 72h)
POST/offline/qrServer-signed offline QR with nonce
POST/offline/noncesPrefetch nonces for a merchant device
POST/offline/syncSubmit signed promises in order; each is SETTLED, REJECTED (with restoreMinor) or DUPLICATE
GET/offline/promisesMy offline promises
GET/diaspora/rate-cardsPublished, platform-signed rate cards (≤ 4h validity)
GET/institutionsVerified institutions and their purpose codes
POST/diaspora/quotesPurpose-locked quote at the current rate card
POST/diaspora/quotes/{id}/payPay a quote from the source-currency wallet
POST/paymentsCreate an interinstitutional payment (IDM-001/002/003; route computed server-side)
GET/payments/{id}Five status dimensions and journal
POST/payments/{id}/cancelCancel before emission (never after)
POST/payment_intents/{id}/captureCapture an AUTHORISED intent (capture_method manual); optional partial amount, the rest returns to the payer
POST/refunds/{id}/rejectReject a refund awaiting execution (REQUESTED / APPROVED): reservation released, intent keeps its prior state
GET/payment_resolutionDid this payment happen? CONFIRMED (ledger posting), PENDING, AMBIGUOUS or NOT_FOUND, with the matches
POST/fx/quotesLock an FX quote (mid-market rate, margin, fee, recipient amount); usable as quote_id on transfers and routes
GET/fx/quotes/{id}Retrieve an FX quote (LOCKED, INDICATIVE or EXPIRED)
POST/money_requestsRequest a payment from a named payer (@tag, phone or email)
GET/money_requests/{code}Retrieve a money request (requester or payer)
POST/money_requests/{code}/cancelCancel an unpaid money request
GET/transactions/{id}A ledger transaction with its balanced entries and the payment intent it settled
POST/api_keys/{id}/rotateRotate a key (same label, kind, scopes, allowlist; old secret revoked). Live keys need PIN / passkey step-up (403 step_up_required)
GET/webhook_events/typesEvent catalogue with the receiver rules (`notes`): at-least-once, dedupe by event id
GET/settlements/{id}A settlement (cycle) with its items and statement summary: provider fee, BitriPay fee and tax on it as separate lines, plus the disclosed conversion when the settlement currency differs
GET/settlement_profiles/{id}/previewWhat the next cycle of a profile would settle now: collection-currency totals with the fee lines, and the conversion into the settlement currency (rate, mid rate, margin bps)
GET/settlement_profiles/{id}Retrieve a settlement profile (settlement_currency, auto_convert)
GET/payment_intents/{id}/split_refundsWhat each split recipient gave back on the refunds of an intent (pro_rata) or kept (merchant_absorbs)
POST/payment_intents/{id}/split_refunds/retryRetry failed split refund recoveries
POST/accountsCreate a customer's merchant account (user, organisation, wallets) that your key can act for with the BitriPay-Account header
GET/accountsList your connected accounts
GET/accounts/{id}A connected account: status, verification, onboarding, balances
PATCH/accounts/{id}Change the default application fee or metadata
POST/accounts/{id}/account_linksA one-time claim link (7 days) the customer opens to set a password and own the account
POST/accounts/{id}/detachEnd your access; the account, its keys and its money stay the customer's