API Reference
The machine-to-machine API you integrate against with a merchant API
key. Everything lives under /v1/api/* and is
authenticated with your key as a Bearer token.
Base URL
https://api.soxara.comOne host for both modes — there is no separate sandbox host. Test vs. live
is decided by your key prefix (sxm_test_… vs sxm_live_…). See
Environments.
Conventions
- Auth:
Authorization: Bearer sxm_live_…on every request. - Content type:
application/json. - Amounts: integer minor units (
1200=USD 12.00). See Money. - Idempotency:
Idempotency-Keyheader (or a body field where noted) on every create. See Idempotency. - Envelopes:
{ "success": true, "data": … }or{ "success": false, "error": { "code", "message" } }. See Errors. - Field casing: request + response bodies are snake_case.
- Scopes:
payments:createto write,payments:readto read. See Scopes.
Payment links
A one-time request to pay. Create one, show the customer the hosted checkout
(https://checkout.soxara.com), and settle via webhook.
Create a payment link
POST /v1/api/payment-links scope: payments:create{
"title": "Order #1234", // required
"description": "…", // optional
"amount": 1200, // minor units; omit for a pay-any-amount link
"currency": "USD", // ISO-4217; one currency per link
"accepted_methods": ["card", "momo", "wallet"], // subset; default all three
"expires_at": "2026-07-10T18:00:00Z" // absolute timestamp, optional
}Returns the link, including link_code (e.g. PL-XXXXXXXX). Compose the payer
URL as https://checkout.soxara.com/{link_code}. There is no checkout_url
field. total_collected / payment_count reflect settlement — but use webhooks,
not polling, to react.
Read a payment link
GET /v1/api/payment-links/{id} scope: payments:read
GET /v1/api/payment-links/by-code/{code} scope: payments:readSimulate a payment (sandbox only)
POST /v1/api/payment-links/{id}/simulate scope: payments:createTest links only (a live link 404s). Creates + settles a payment through the
exact same fan-out as production, so you receive a real webhook without real
money. Body (all optional): { "method": "card|momo|wallet", "outcome": "completed|failed", "amount": <minor units, pay-any links only> }.
Payments
Retrieve a payment
GET /v1/api/payments/{id} scope: payments:readWorks for both a link payment and a recurring charge. The status-by-id read for reconciliation or a missed webhook.
List payments (transaction history)
GET /v1/api/payments?status=&before=&limit= scope: payments:readYour payments — link payments and mandate charges — newest first.
| Param | |
|---|---|
status | pending | completed | failed (optional) |
before | ISO-8601 cursor; returns rows created before it (keyset pagination) |
limit | default 50, max 100 |
The response carries next_before (the last row’s created_at) — pass it back
as before for the next page. Scoped to the key’s environment.
Payment summary (dashboard metrics)
GET /v1/api/payments/summary?since= scope: payments:readPer-currency roll-up for dashboard tiles:
{ "summary": [
{ "currency": "USD",
"completed_count": 42, "completed_amount": 504000, // revenue
"failed_count": 3, "pending_count": 0, "total_count": 45 }
]}Recurring billing
Charge a customer each cycle without re-approval. A mandate is the standing authorization; a charge draws against it. Two rails:
- Card — the customer saves a card once on a hosted page (no Soxara account needed). Carries the card-processing fee (USD-only).
- Wallet — a Soxara wallet holder approves in-app with a PIN. Fee-free, multi-currency.
The typical flow: create a mandate at sign-up (customer approves once), then your
own scheduler calls POST /v1/api/charges each cycle and reacts to the
payment.completed / payment.failed webhook.
Create a mandate
POST /v1/api/mandates scope: payments:create{
"rail": "card", // "card" | "wallet"
"customer_ref": "your-user-id", // your id for this customer; echoed back
"currency": "USD", // card is USD-only; wallet allows USD/LRD
"max_amount_cents": 1200, // per-charge cap
"customer_email": "…" // optional (card receipts)
}- Card: returns
{ mandate, approval_url, setup_intent_client_secret }. Send the customer toapproval_url(https://checkout.soxara.com/mandate/{id}) to save a card. The mandate activates when they do. - Wallet: returns a
pendingmandate + anapproval_url(https://checkout.soxara.com/wallet-mandate/{id}) the customer opens in their Soxara app to approve with a PIN. - Test keys create an already-active mandate so you can exercise charges without the approval step.
Charge a mandate
POST /v1/api/charges scope: payments:create{
"mandate_id": "…",
"amount_cents": 1200, // must be ≤ the mandate's max_amount_cents
"currency": "USD",
"idempotency_key": "sub_42:2026-07" // your cycle key
}Returns the payment synchronously with its status, and fires the same
payment.completed / payment.failed webhook. A card decline / insufficient
wallet balance comes back as a failed payment (so you can dun). In a test
mandate, add "outcome": "completed|failed" to force the result.
Read / revoke a mandate
GET /v1/api/mandates/{id} scope: payments:read
DELETE /v1/api/mandates/{id} scope: payments:create (stops future charges)Webhooks
Register where Soxara POSTs events, with the same key you pay with.
POST /v1/api/webhook-endpoints scope: payments:create (returns the secret ONCE)
GET /v1/api/webhook-endpoints scope: payments:read
DELETE /v1/api/webhook-endpoints/{id} scope: payments:createSee Webhooks for the payload shape, signature verification, and the event list.
What this API does not do
- Card issuance / KYC — your customers do these inside Soxara’s own surfaces.
- Consumer wallet/transfer/bill/membership APIs — those are Soxara’s own JWT-authenticated app surfaces, not the merchant key surface.
- Refunds on
/v1/api/*— not yet exposed (coming).
OpenAPI
A generated OpenAPI 3.1 spec is on the roadmap. Until then, this page is the source of truth; ping [email protected] for any shape you can’t find here.