{
"grant_type": "client_credentials",
"client_id": "fex_…",
"client_secret": "…"
}
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 1200
}
Reference
Integrate once. Offer bills, investments, insurance, and more through a stable /v1 surface.
http://fex.mojo-pay.com/v1
The FEX Partner API lets banks and fintechs distribute financial products through one integration. You never call product providers or MojoPay directly — catalog, quotes, orders, payments, and webhooks all go through FEX.
client_id / client_secret pair (the secret is shown once — store it securely).POST /oauth/token.input_schema.payment.url (MojoPay Omni hosted checkout when configured).GET /orders/{id}.http://fex.mojo-pay.com/v1. API version in webhooks: 2026-06-01.
client_secret; if it leaks, rotate the secret (same client_id). Secrets cannot be revoked. The same /v1 base URL is used for sandbox and live — your partner tier controls catalog visibility and payment rails.
sandbox or active, and complete payment on FEX’s hosted sandbox pay page. When MojoPay promotes you to a live tier (standard / enterprise), the catalog returns active products only and payment.url uses MojoPay Omni. Product codes and request shapes stay the same — only eligibility and payment change.
This page covers what every integration shares. Each product guide is a complete, self-contained API walkthrough for one product line — products, fields, lookups, quote and order examples, and webhooks.
OAuth 2.0 client credentials. Tokens are opaque (not JWT) and expire after about 20 minutes.
| Header | Required | Description |
|---|---|---|
Authorization |
Yes* | Bearer <token> (*except token endpoint) |
Content-Type |
POST | application/json |
Accept |
Recommended | application/json |
X-FEX-Country |
No | ISO country (e.g. GH). Defaults to partner profile. |
Idempotency-Key |
Recommended | On POST /orders — replay-safe create. |
{
"grant_type": "client_credentials",
"client_id": "fex_…",
"client_secret": "…"
}
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 1200
}
Failed responses use a consistent envelope. Use error.code for programmatic handling and request_id when contacting support.
{
"error": {
"code": "validation_failed",
"message": "Product fields failed validation.",
"details": { "errors": { "amount": ["required"] } },
"request_id": "req_…"
}
}
unauthorized
not_found
product_unavailable
validation_failed
quote_unavailable
quote_expired
invalid_state
Catalog responses are FEX products. Each includes an input_schema that drives quote and order field shapes. Provider-native endpoints are never exposed.
GET /catalog/products (and read each input_schema). MojoPay enables products per partner in Admin; your credentials only see those products (still filtered by tier, country, and status). These are FEX products used on quotes/orders (hb-*, st-*, bs-*, ic-*, hi-*, xo-*, ml-*) — not SecondsTax mutual-fund names or Xoxoday / MojoLife voucher SKUs. For investable SecondsTax funds, use GET /customers/{id}/provider-funds. For Xoxoday gift vouchers, use GET /providers/{id}/vouchers then quote xo-gift-voucher; for MojoLife gift cards and vouchers, list PRV_MOJOLIFE the same way and quote ml-gift-card / ml-voucher. New products appear without a Partner API version change once enabled for you.
| Provider | Example codes | Categories | Products | Guide |
|---|---|---|---|---|
Black Star (black-star) |
bs-fund-deposit bs-sell-order |
investments |
2 | Investments |
Hollard Insurance (hollard) |
hi-home-policy hi-motor-policy hi-travel-policy |
insurance_general |
3 | Insurance |
| FEX | hb-at-airtime hb-mtn-airtime hb-telecel-airtime +9 more |
airtime bills data |
12 | Airtime & data, Bills & utilities |
IC Securities (ic-securities) |
ic-fund-investment ic-equity-market-order ic-fixed-income-order |
investments stocks treasury |
3 | Investments |
MojoLife (mojolife) |
ml-deal ml-gift-card ml-voucher |
deals gift_cards |
3 | Rewards |
| FEX | ppn-topup ppn-gift-card |
airtime gift_cards |
2 | Airtime & data, Rewards |
SecondsTax (secondstax) |
st-fund-redemption st-fund-subscription |
investments |
2 | Investments |
Xoxoday Canvas (xoxoday) |
xo-gift-voucher xo-experience xo-physical-voucher |
gift_cards |
3 | Rewards |
Orientation only — this is the FEX product catalog (what you pass as product_id on quotes), not provider fund pickers. SecondsTax exposes two FEX products (st-fund-subscription, st-fund-redemption); the many mutual funds (Slytherin, etc.) come from GET /customers/{id}/provider-funds. Top-ups use the same subscription product + same fund_id again. Your API credentials return a partner-scoped subset via GET /v1/catalog/products.
GET /catalog/categories
Authorization: Bearer <token>
{
"object": "list",
"data": [
{ "code": "bills", "object": "category" },
{ "code": "investments", "object": "category" },
{ "code": "insurance_general", "object": "category" },
{ "code": "stocks", "object": "category" }
]
}
GET /catalog/products
Authorization: Bearer <token>
{
"object": "list",
"data": [
{
"id": "PRD_HB_ECG",
"object": "product",
"code": "hb-ecg-bill",
"name": "ECG Prepaid & Postpaid",
"category": "bills",
"country": "GH",
"currency": "GHS",
"status": "sandbox",
"input_schema": {
"type": "object",
"required": ["phone", "meter_number", "amount"],
"properties": {
"phone": { "type": "string" },
"meter_number": { "type": "string" },
"amount": { "type": "number" }
}
},
"min_amount": "1.00",
"max_amount": null,
"provider": { "id": "PRV_…", "code": "…", "name": "…" },
"lookup_url": "https://…/v1/catalog/products/PRD_HB_ECG/lookup"
}
]
}
GET /catalog/products/{id}
Authorization: Bearer <token>
{
"id": "PRD_HB_ECG",
"object": "product",
"code": "hb-ecg-bill",
"name": "ECG Prepaid & Postpaid",
"category": "bills",
"input_schema": {
"type": "object",
"required": ["phone", "meter_number", "amount"],
"properties": {
"phone": { "type": "string", "title": "Phone linked to the meter" },
"meter_number": { "type": "string", "title": "Meter / account number" },
"amount": { "type": "number", "title": "Amount (GHS)" }
}
},
"provider": { "id": "PRV_…", "code": "…", "name": "…" },
"lookup_url": "https://…/v1/catalog/products/PRD_HB_ECG/lookup"
}
{
"fields": {
"phone": "0246912184"
}
}
{
"object": "lookup",
"product_id": "PRD_HB_MTN_DATA",
"type": "bundles",
"label": "Successful",
"items": [
{ "display": "349.24MB", "value": "data_bundle_3", "amount": "3.00" },
{ "display": "1.19GB", "value": "flexi_data_bundle", "amount": "20.00" }
],
"account": null
}
Price a product for the submitted fields. For data bundles and billers that support lookup, FEX runs the lookup during quote and returns it as lookup (e.g. the account name) so you can confirm with the customer before paying. Quotes expire — create the order before expires_at. Bill, airtime and data purchases are asynchronous: after payment the order stays fulfilling until the biller or network confirms, then you receive order.completed or order.failed.
{
"product_id": "PRD_HB_ECG",
"fields": {
"phone": "0541312238",
"meter_number": "P09137104",
"amount": 12.5
}
}
{
"id": "QTE_…",
"object": "quote",
"product_id": "PRD_HB_ECG",
"currency": "GHS",
"amount": "12.50",
"fex_fee_amount": "0.00",
"mojopay_fee_amount": "0.00",
"amount_payable": "12.50",
"input_fields": {
"phone": "0541312238",
"meter_number": "P09137104",
"amount": 12.5
},
"expires_at": "2026-08-30T15:00:00+00:00",
"lookup": {
"type": "meters",
"label": "Successful",
"items": [
{ "display": "THOMAS ANANE (P09137104)", "value": "P09137104", "amount": "-1.14" }
],
"account": null
}
}
GET /quotes/{id}
Authorization: Bearer <token>
Lifecycle: pending_payment → paid → fulfilling → completed (or failed / cancelled / expired).
{
"quote_id": "QTE_…",
"partner_reference": "INV-1001",
"payment_method": "checkout",
"success_url": "https://partner.example/success",
"cancel_url": "https://partner.example/cancel",
"customer": { "email": "customer@example.com" },
"metadata": { "channel": "mobile_app" }
}
{
"id": "ORD_…",
"object": "order",
"status": "pending_payment",
"partner_reference": "INV-1001",
"currency": "GHS",
"amount": "50.00",
"amount_payable": "50.00",
"product_id": "PRD_…",
"payment": {
"id": "CS_…",
"method": "checkout",
"status": "processing",
"url": "https://omni.mojo-pay.com/pay/CS_…",
"merchant_reference": "FEX-ORD_…-1"
},
"failure": null,
"created_at": "…",
"completed_at": null
}
GET /orders/{id}
Authorization: Bearer <token>
GET /orders
Authorization: Bearer <token>
{
"object": "list",
"data": [
{ "id": "ORD_…", "object": "order", "status": "completed" }
]
}
POST /orders/{id}/cancel
Authorization: Bearer <token>
Each API client under Partner Portal → API & webhooks can have its own webhook_url and signing secret. FEX fans out order events to every active client with a URL configured, signing each delivery with that client’s secret (HMAC-SHA256, FEX-Signature).
Header format:
FEX-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>Signed payload: {timestamp}.{raw_body} using your webhook_secret.
{
"id": "EVT_…",
"type": "order.completed",
"api_version": "2026-06-01",
"created": "2026-08-25T14:30:00+00:00",
"data": {
"object": {
"id": "ORD_…",
"object": "order",
"status": "completed"
}
}
}
| Type | When |
|---|---|
order.pending_payment |
Order created; customer should pay |
order.paid |
Payment confirmed |
order.fulfilling |
Provider fulfillment started |
order.completed |
Terminal success |
order.failed |
Terminal failure |
order.cancelled |
Cancelled by partner or system |
Respond with 2xx quickly. Delivery is at-least-once — dedupe on event id.
| Route group | Limit |
|---|---|
POST /oauth/token |
30 requests / minute |
Authenticated /v1/* |
120 requests / minute |