MFS Sandbox Gateway — API reference

← Back to dashboard

Authentication

Every route below requires Authorization: Bearer <SANDBOX_RAIL_API_KEY>, except those marked “no auth”. Wrong or missing key → 401.

All routes share one rate limit: SANDBOX_RAIL_RATE_LIMIT requests (default 120) per rolling 60s window. Exceeding it → 429 with a Retry-After header.

Endpoints

GET/healthno auth

200 if service + Supabase are reachable, non-200 otherwise.

GET/api/v1/referenceno auth

The test-MSISDN table as JSON, including the dummy OTP.

POST/api/v1/mfs/charges

Requires an Idempotency-Key header. A repeated request with the same key returns the original result, never creates a second charge.

{ "msisdn": "+8801700000001", "amount": 29900, "currency": "BDT", "redirect_uri": "https://engine.example.com/mfs/callback" }

→ 202:

{
  "id": "ch_...", "msisdn": "...", "amount": 29900, "currency": "BDT",
  "status": "pending_confirmation", "outcome": "PENDING",
  "confirmation_id": "conf_...",
  "confirmation_url": "http://localhost:3000/mfs-checkout/conf_...",
  "created_at": "..."
}

confirmation_url is a real, working hosted page — this is what a consumer (in practice, the Subscription Engine) passes straight through as its own checkout_url. Not a display string.

POST/api/v1/mfs/confirm/:confirmationId

Requires an OTP field. Dummy OTP for every test MSISDN: 123456.

{ "otp": "123456" }

Any other value returns 400 with a generic incorrect-code error — never a hint that the value is fixed. On a correct OTP, resolves per the MSISDN's test-value behavior. Scoped to one confirmation attempt for onboarding — recurring debit means this is called once, at consent, not on every billing cycle.

GET/api/v1/mfs/charges/:id

Status: pending_confirmation | pending_retry | succeeded | failed | expired.

POST/api/v1/mfs/charges/:id/renew

Not in the original engine contract — added as an extension, not yet coordinated with the engine team. See CHANGELOG.md.

Simulates a silent, engine-initiated recharge on an already-established mandate. No OTP. :id is the mandate's charge id (must already be succeeded).

Requires an Idempotency-Key header. A repeated request with the same key returns the original result rather than creating a second renewal.

{ "mandate_id": "ch_..." }

mandate_id must equal the path :id. Returns 201 with a new charge row for that billing cycle, resolved immediately via the same per-MSISDN outcome table used at consent.

POST/api/v1/reset

Clears all MFS state (mfs_charges, events). merchant_redirect_allowlist is configuration, not transactional data, and is not cleared by reset.

Error shape

{ "error": { "code": "snake_case_code", "message": "..." } }

Test MSISDNs

See the dashboard or GET /api/v1/reference for the full table of deterministic test MSISDNs, their outcomes, and decline codes.

Contract source: modules/02-api-routes.md. Route paths, field names, and outcome value meanings are load-bearing for the Subscription Engine — see INTEGRATION.md before changing any of them.