Developers

API reference

65 operations across 16 areas. Every response uses the same envelope: { ok: true, data, meta? } on success, { ok: false, error: { code, message, correlationId } } on failure. Send x-correlation-id to trace a request end-to-end; quote it in support tickets.

Authentication

Web clients ride the vp_access HttpOnly cookie (CSRF-protected). Mobile and server clients send Authorization: Bearer <accessToken> and rotate with the refresh token.

Idempotency

POST /api/v1/orders requires a per-user idempotencyKey. Replaying it returns the original order — never a second charge.

Money

Amounts are integer minor units (amountMinor) computed server-side. The decimal string is display-only. No endpoint accepts a client-computed price.

System

Health, provider status and the contract itself

get/api/v1/healthPublic

Liveness/readiness probe

Returns `ok` or `degraded` plus database/provider checks. Safe to wire to a container healthcheck or uptime monitor. Public.

Responses

  • 200 Service status → object
get/api/v1/openapiPublic

This OpenAPI document

Machine-readable contract generated from the live zod validation schemas. Import into Postman/Insomnia/Stoplight.

Responses

  • 200 OpenAPI 3.1 document
get/api/v1/provider/healthAuthenticated

Upstream provider health

Performs a live balance call against SMSPool when credentials are configured, otherwise reports the mock provider. Never returns the API key.

Responses

  • 200 Success → ProviderHealth
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Auth

Registration, sign-in, rotating sessions, password reset

post/api/v1/auth/registerPublic

Create a customer account

Creates the user plus a zero-balance wallet, sets session cookies (web) and returns an access token + rotating refresh token (mobile). Rate limited per IP.

Request body

FieldTypeNotes
email*string (email)—
password*string—
displayNamestring—
referralCodestring—

Responses

  • 201 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/auth/loginPublic

Sign in

argon2id verification, per-account + per-IP throttling. Returns tokens; the web client receives them as HttpOnly cookies and never sees the refresh token in JS.

Request body

FieldTypeNotes
email*string (email)—
password*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/auth/refreshAuthenticated

Rotate the session

Single-use rotating refresh token. Reuse of an already-rotated token revokes the whole token family and returns 401. Web sends the cookie; mobile sends `refreshToken` in the body.

Request body

FieldTypeNotes
refreshTokenstring—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/auth/logoutAuthenticated

Sign out

Revokes the presented refresh token (and clears cookies).

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/auth/forgot-passwordPublic

Request a password reset link

Always returns 200 to avoid account enumeration. In DEMO_MODE with the log email transport the response includes `devToken` for local testing; production returns `{ requested: true }` only.

Request body

FieldTypeNotes
email*string (email)—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/auth/reset-passwordPublic

Complete a password reset

Consumes a single-use reset token, sets the new argon2id hash and revokes every active session for that user.

Request body

FieldTypeNotes
token*string—
newPassword*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Account

Profile, password, notifications

get/api/v1/meAuthenticated

Current profile

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/meAuthenticated

Update profile

Update display name and/or email address.

Request body

FieldTypeNotes
displayNamestring—
emailstring (email)—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/me/passwordAuthenticated

Change password

Requires the current password; revokes other sessions after the change.

Request body

FieldTypeNotes
currentPassword*string—
newPassword*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/notificationsAuthenticated

List notifications

Query parameters

  • page (integer)
  • pageSize (integer)
  • unreadOnly ("true" | "false")

Responses

  • 200 Paged success → Notification[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/notifications/{id}/readAuthenticated

Mark one notification read

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/notifications/read-allAuthenticated

Mark all notifications read

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Catalog

Countries and services available upstream

get/api/v1/countriesPublic

List serviceable countries (public)

Served from the latest catalog snapshot. Availability is populated by the catalog-refresh job via the provider adapter.

Query parameters

  • search (string)

Responses

  • 200 Success → Country[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/servicesPublic

List services (public)

Query parameters

  • countryProviderId (integer)
  • search (string)

Responses

  • 200 Success → Service[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Pricing

Margin-adjusted, server-computed prices

get/api/v1/pricingAuthenticated

Marketplace price list

Server-computed sell price for every country/service pair (integer math only). Prices already include the configured margin rules; the client must never compute prices.

Query parameters

  • countryId (string (uuid))
  • search (string)
  • availableOnly ("true" | "false")
  • sort ("price_asc" | "price_desc" | "country" | "service")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → PricingRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/pricing/quoteAuthenticated

Price quote for one pair

Live cost/stock lookup plus local margin rules. `warnings` carries operator-visible caveats such as demo/mock mode. The quote is advisory — the authoritative charge is re-computed inside the purchase transaction.

Query parameters

  • countryId (string (uuid))
  • serviceId (string (uuid))

Responses

  • 200 Success → Quote
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Orders

Purchases, OTP retrieval, cancellation, live status

get/api/v1/ordersAuthenticated

List your orders

Ownership-scoped. Phone numbers are masked; OTP codes are never included in list responses.

Query parameters

  • status (string) — Comma-separated order statuses
  • search (string)
  • from (string (date-time))
  • to (string (date-time))
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → Order[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/ordersAuthenticated

Buy a number / request an OTP

Idempotent: `idempotencyKey` is unique per user, so a double-click or a retried request returns the original order instead of charging twice. Funds are reserved first, then the provider purchase is attempted exactly once. A provider timeout is NOT treated as a failure — the order becomes UNCERTAIN and reconciliation decides (refund only after the provider confirms).

Request body

FieldTypeNotes
countryId*string (uuid)—
serviceId*string (uuid)—
idempotencyKey*string—
poolProviderIdinteger | null—

Responses

  • 200 Duplicate idempotency key — the original order is returned → Order
  • 201 Order created (or the existing order for this idempotency key) → Order
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/orders/exportAuthenticated

Export order history as CSV

Streams CSV of your own orders. OTP codes are intentionally excluded from exports.

Query parameters

  • from (string (date-time))
  • to (string (date-time))

Responses

  • 200 CSV file
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/orders/{id}Authenticated

Order detail (by id or public ref)

Owner-scoped. Includes the OTP code, the full phone number and the status timeline once available.

Path parameters

  • id — string

Responses

  • 200 Success → Order
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/orders/{id}/cancelAuthenticated

Cancel an active order

Only pre-SMS states can be cancelled. The refund is applied **only** when the provider confirms the cancellation; otherwise the order is released back to its previous state or becomes UNCERTAIN for reconciliation.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → Order
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/orders/{id}/eventsAuthenticated

Live order status stream (SSE)

Server-Sent Events stream of status changes for one order, owner-scoped. Clients fall back to polling `GET /api/v1/orders/{id}` when SSE is unavailable.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 text/event-stream of `{ "status": …, "at": … }` frames
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Wallet

Balances and the immutable ledger

get/api/v1/walletAuthenticated

Wallet balances

Available/reserved/total in integer minor units. Balances are server-authoritative; no endpoint accepts a client-supplied balance.

Responses

  • 200 Success → Wallet
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/wallet/transactionsAuthenticated

Ledger entries

Append-only double-entry-style ledger rows for the caller, newest first.

Query parameters

  • category ("DEPOSIT_CREDIT" | "PURCHASE_RESERVE" | "PURCHASE_DEBIT" | "PURCHASE_RELEASE" | "REFUND_CREDIT" | "ADJUSTMENT")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → WalletTransaction[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/wallet/statementAuthenticated

Download a statement (CSV)

Query parameters

  • from (string (date-time))
  • to (string (date-time))

Responses

  • 200 CSV file
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Deposits

Manual bKash/Nagad top-ups with admin verification

get/api/v1/deposits/methodsAuthenticated

Deposit methods + instructions

Returns the operator-configured bKash/Nagad merchant numbers and step-by-step instructions. Manual deposits always require human verification before funds are credited.

Responses

  • 200 Success → DepositMethods
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/depositsAuthenticated

List your deposits

Query parameters

  • status ("PENDING" | "APPROVED" | "REJECTED" | "EXPIRED")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → Deposit[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/depositsAuthenticated

Declare a deposit

Creates a PENDING deposit with a unique reference. For Stripe, generates a checkout session URL. For manual methods, is credited upon admin approval.

Request body

FieldTypeNotes
method*"MANUAL_BKASH" | "MANUAL_NAGAD" | "GATEWAY" | "STRIPE" | "COINGATE"—
amount*string—
senderRefstring—
senderAccountstring—
successUrlstring (uri)—
cancelUrlstring (uri)—

Responses

  • 201 Success → Deposit
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/deposits/stripe/verifyAuthenticated

Verify Stripe checkout session

Queries the Stripe API to verify completion of a checkout session and immediately credits the user wallet.

Request body

FieldTypeNotes
sessionId*string—
depositIdstring—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/deposits/stripe/payment-intentAuthenticated

Create Stripe in-app payment intent

Initializes a Stripe PaymentIntent for in-app mobile or web checkout with optional instant card confirmation.

Request body

FieldTypeNotes
amount*string—
cardobject—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/deposits/coingate/verifyAuthenticated

Verify CoinGate crypto payment

Queries the CoinGate API to verify payment of a cryptocurrency order and immediately credits the user wallet.

Request body

FieldTypeNotes
orderId*object—
depositIdstring—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Payments

Signed gateway webhooks

post/api/v1/payments/webhook/{gatewayId}Public

Payment gateway webhook (server-to-server)

HMAC-SHA256 signed webhook ingress (`x-signature`, optional `sha256=` prefix, `x-event-id` for idempotency). Disabled unless configured. Signature verified against the raw body before any credit; duplicate events are ignored; amounts/currencies are re-verified server-side.

Path parameters

  • gatewayId — string

Request body

No structured body — see the description.

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/payments/webhook/coingatePublic

CoinGate cryptocurrency webhook ingress (server-to-server)

Processes status: paid CoinGate webhook events to verify and credit wallet deposits.

Request body

No structured body — see the description.

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/payments/webhook/stripePublic

Stripe webhook ingress (server-to-server)

Processes checkout.session.completed and payment_intent.succeeded Stripe webhook events to verify and credit wallet deposits.

Request body

No structured body — see the description.

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Support

Customer tickets

get/api/v1/support/ticketsAuthenticated

List your tickets

Query parameters

  • status (string)
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → Ticket[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/support/ticketsAuthenticated

Open a ticket

Request body

FieldTypeNotes
subject*string—
category*"ORDER" | "PAYMENT" | "ACCOUNT" | "OTHER"—
priority"LOW" | "NORMAL" | "HIGH" | "URGENT"—
body*string—

Responses

  • 201 Success → Ticket
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/support/tickets/{id}Authenticated

Ticket thread

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → Ticket
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/support/tickets/{id}/replyAuthenticated

Reply to a ticket

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
body*string—

Responses

  • 200 Success → Ticket
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Users

User administration

get/api/v1/admin/usersAuthenticated

Search users

Requires the `users:read` permission.

Query parameters

  • search (string)
  • status ("ACTIVE" | "SUSPENDED" | "CLOSED")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → AdminUserRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/users/{id}Authenticated

User detail (wallet, orders, notes)

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → AdminUserRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/admin/users/{id}/statusAuthenticated

Suspend / reactivate a user

Audited. Suspension blocks authentication and new purchases but never touches balances.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
status*"ACTIVE" | "SUSPENDED"—
reason*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/admin/users/{id}/limitsAuthenticated

Set the daily purchase limit

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
purchaseLimitDaily*integer—
reasonstring—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/admin/users/{id}/notesAuthenticated

Attach internal notes

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
notes*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/users/{id}/wallet-adjustAuthenticated

Manual wallet adjustment (compensating entry)

Writes an ADJUSTMENT ledger row — the only supported way to correct a balance, since ledger rows are immutable. Requires a reason and the `wallet:adjust` permission; audited. Step-up authentication is enforced on the web UI.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
amount*string—
reason*string—

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Orders

Order operations and reconciliation

get/api/v1/admin/ordersAuthenticated

Search orders (all users)

Query parameters

  • status (string) — Comma-separated order statuses
  • search (string)
  • providerMode ("LIVE" | "MOCK")
  • uncertainOnly ("true" | "false")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → AdminOrderRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/orders/{id}Authenticated

Order detail (all users)

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → AdminOrderRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/orders/{id}/reconcileAuthenticated

Reconcile an order against the provider

Re-checks the upstream order. When the provider has no record, the order is marked UNCERTAIN, flagged for review (audit + event) and funds stay committed — they are never released automatically. Second invocation escalates to NEEDS_REVIEW.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → ReconcileResult
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Deposits

Deposit approval queue

get/api/v1/admin/depositsAuthenticated

Deposit queue

Manual deposits awaiting verification, oldest first, plus history.

Query parameters

  • status ("PENDING" | "APPROVED" | "REJECTED" | "EXPIRED")
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → AdminDepositRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/deposits/{id}/approveAuthenticated

Approve a manual deposit (credits the wallet)

The single place where manual money enters the ledger: validates the reference, credits DEPOSIT_CREDIT, marks the deposit APPROVED and records the reviewer. Requires `deposits:review`.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
note*string—

Responses

  • 200 Success → AdminDepositRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/deposits/{id}/rejectAuthenticated

Reject a manual deposit

No ledger effect — a rejected deposit never credits anything.

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
note*string—

Responses

  • 200 Success → AdminDepositRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Pricing

Margin rules and catalog sync

get/api/v1/admin/pricing-rulesAuthenticated

List pricing rules

Responses

  • 200 Success → PricingRule[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/pricing-rulesAuthenticated

Create a pricing rule

Rules are integer-only: PERCENT_BPS (basis points, 2500 = 25%) or FIXED_MINOR (minor units), with optional minimum sell price and minimum margin. Most specific scope wins, then priority.

Request body

FieldTypeNotes
scope*"GLOBAL" | "COUNTRY" | "SERVICE" | "COUNTRY_SERVICE"—
countryIdstring | null—
serviceIdstring | null—
ruleType*"PERCENT_BPS" | "FIXED_MINOR"—
value*string—
minSellPriceMinorstring | null—
minMarginBpsinteger | null—
priorityinteger—
activeboolean—

Responses

  • 201 Success → PricingRule
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/admin/pricing-rules/{id}Authenticated

Update a pricing rule

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
scope"GLOBAL" | "COUNTRY" | "SERVICE" | "COUNTRY_SERVICE"—
countryIdstring | null—
serviceIdstring | null—
ruleType"PERCENT_BPS" | "FIXED_MINOR"—
valuestring—
minSellPriceMinorstring | null—
minMarginBpsinteger | null—
priorityinteger—
activeboolean—

Responses

  • 200 Success → PricingRule
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/pricing-previewAuthenticated

Preview a rule against the live catalog

Shows the resulting sell price/margin for a country/service pair before saving a rule.

Query parameters

  • countryId (string (uuid))
  • serviceId (string (uuid))
  • limit (integer)

Responses

  • 200 Success → PricingRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/catalog/syncAuthenticated

Force a catalog refresh

Re-pulls countries/services/stock from the provider adapter and snapshots the result.

Responses

  • 200 Success → object
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Tickets

Support queue for staff

get/api/v1/admin/ticketsAuthenticated

Support queue

Query parameters

  • status (string)
  • priority (string)
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → AdminTicketRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/tickets/{id}Authenticated

Ticket thread (staff view)

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Responses

  • 200 Success → AdminTicketRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
post/api/v1/admin/tickets/{id}/replyAuthenticated

Staff reply

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
body*string—

Responses

  • 200 Success → AdminTicketRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
patch/api/v1/admin/tickets/{id}/statusAuthenticated

Change ticket status / assign

Path parameters

  • id — Internal UUID **or** public ref (NX-…, DP-…) where noted

Request body

FieldTypeNotes
status*"OPEN" | "PENDING_CUSTOMER" | "ASSIGNED" | "RESOLVED" | "CLOSED"—
assignToSelfboolean—

Responses

  • 200 Success → AdminTicketRow
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Admin: Insights

Metrics and audit trail

get/api/v1/admin/dashboardAuthenticated

Operations dashboard metrics

User/order counters, uncertain-order count, wallet liability and margin for the current period.

Responses

  • 200 Success → AdminDashboard
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/metrics/timeseriesAuthenticated

Time series for charts

Query parameters

  • metric ("orders" | "revenue" | "margin" | "deposits")
  • days (integer)

Responses

  • 200 Success → object[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable
get/api/v1/admin/audit-logsAuthenticated

Immutable audit trail

Every privileged action lands here. `redactedMeta` is sanitised before storage — OTPs, tokens, API keys and passwords never appear.

Query parameters

  • action (string)
  • targetType (string)
  • actorId (string (uuid))
  • page (integer)
  • pageSize (integer)

Responses

  • 200 Paged success → AuditLogRow[]
  • 400 Malformed request
  • 401 Not authenticated / token expired
  • 403 Forbidden — missing permission, suspended account, or CSRF/origin rejected
  • 404 Not found (or not owned by the caller)
  • 409 Conflict — duplicate idempotency key or illegal state transition
  • 422 Validation failed
  • 429 Rate limit exceeded
  • 503 Upstream provider or gateway unavailable

Try it locally

# 1. sign in (web cookie flow)
curl -sX POST http://localhost:3000/api/v1/auth/login \
  -H 'content-type: application/json' \
  -c cookies.txt \
  -d '{"email":"you@example.com","password":"…"}'

# 2. see live prices
curl -s "http://localhost:3000/api/v1/pricing?pageSize=5&sort=price_asc" -b cookies.txt

# 3. buy a number (idempotent — safe to retry)
curl -sX POST http://localhost:3000/api/v1/orders -b cookies.txt \
  -H 'content-type: application/json' \
  -d '{"countryId":"…","serviceId":"…","idempotencyKey":"order-$(date +%s)"}'

# 4. read the OTP once it arrives
curl -s http://localhost:3000/api/v1/orders/NX-XXXXXX -b cookies.txt

Machine clients can authenticate with a Bearer token instead of cookies and poll GET /api/v1/orders/{id} if SSE is not available.

Questions or an endpoint you need? Contact support or open a ticket — include the x-correlation-id from the failing call.