API ReferenceOverview

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.com

One 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-Key header (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:create to write, payments:read to read. See Scopes.

A one-time request to pay. Create one, show the customer the hosted checkout (https://checkout.soxara.com), and settle via webhook.

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.

GET /v1/api/payment-links/{id}              scope: payments:read
GET /v1/api/payment-links/by-code/{code}    scope: payments:read

Simulate a payment (sandbox only)

POST /v1/api/payment-links/{id}/simulate    scope: payments:create

Test 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:read

Works 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:read

Your payments — link payments and mandate charges — newest first.

Param
statuspending | completed | failed (optional)
beforeISO-8601 cursor; returns rows created before it (keyset pagination)
limitdefault 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:read

Per-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 to approval_url (https://checkout.soxara.com/mandate/{id}) to save a card. The mandate activates when they do.
  • Wallet: returns a pending mandate + an approval_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:create

See 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.