Developers

Partner API Product guide

Rewards

Xoxoday e-gift vouchers, experiences and physical vouchers, plus MojoLife gift cards, vouchers and deals, for Ghana, billed in GHS.

Base URL http://fex.mojo-pay.com/v1
Open API tester

Overview

Everything you need to sell rewards through the FEX Partner API. All paths are relative to the /v1 base URL above. Shared concepts (sandbox vs live, rate limits) are on the Partner API overview.

Integration flow

  1. Get a Bearer token with POST /oauth/token.
  2. List reward brands with GET /providers/PRV_XOXODAY/vouchers?product_code=xo-gift-voucher, or MojoLife SKUs with GET /providers/PRV_MOJOLIFE/vouchers?product_code=ml-gift-card (or ml-voucher).
  3. Create a quote for the matching FEX product with the reward product_id and the receiver’s email. Xoxoday also needs a face-value denomination and contact.
  4. Create an order from the quote and send the customer to payment.url (always billed in GHS).
  5. Wait for order.completed. Xoxoday delivers the voucher to the receiver; MojoLife returns the code in provider_details.voucher_code.
  6. MojoLife deals (ml-deal) are paid on the MojoLife checkout: payment.method is provider_checkout and the confirmation code arrives with order.completed.

Products

Product Name Provider Required fields Lookup
PRD_ML_DEAL
ml-deal
MojoLife Deals MojoLife product_id buyer_name email —
PRD_PPN_GIFT_CARD
ppn-gift-card
International Gift Card FEX sku_id amount email Yes
PRD_ML_GIFT_CARD
ml-gift-card
MojoLife Gift Card MojoLife product_id email —
PRD_ML_VOUCHER
ml-voucher
MojoLife Voucher MojoLife product_id email —
PRD_XO_GIFT_VOUCHER
xo-gift-voucher
Xoxoday E-Gift Voucher Xoxoday Canvas product_id denomination email contact —
PRD_XO_EXPERIENCE
xo-experience
Xoxoday Experience Xoxoday Canvas product_id denomination email contact —
PRD_XO_PHYSICAL
xo-physical-voucher
Xoxoday Physical Voucher Xoxoday Canvas product_id denomination email contact —

Orientation only — your credentials see a partner-scoped subset. Always call GET /catalog/products and read each input_schema rather than hardcoding this table.

Authentication

Create a client_id / client_secret under Partner Portal → API credentials, then exchange them for a Bearer token (about 20 minutes). Send Authorization: Bearer <token> on every other call.

Global headers

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.
POST /oauth/token No auth

Issue a short-lived Bearer access token.

Send Authorization: Bearer <access_token> on all other routes.

Body parameters

client_id required
string
From Partner Portal → API credentials.
client_secret required
string
Shown once when you generate or rotate credentials in the Partner Portal.
grant_type optional
string
Optional. Must be client_credentials when sent.
POST http://fex.mojo-pay.com/v1/oauth/token
Request
{
  "grant_type": "client_credentials",
  "client_id": "fex_…",
  "client_secret": "…"
}
Response 200
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 1200
}

Rewards (Xoxoday)

Xoxoday Plum rewards for Ghana only: e-gift vouchers, experiences, and physical vouchers. FEX catalog products are umbrellas: xo-gift-voucher, xo-experience, xo-physical-voucher (under gift_cards). Brand SKUs come from GET /providers/{id}/vouchers?product_code=xo-gift-voucher (always Ghana). Then quote the matching FEX product with Plum product_id + native denomination (SKU face currency) + receiver email/contact. Quotes and MojoPay always bill GHS — FEX converts foreign face values using Admin → Settings → FX rates (rate + markup). Provider id: PRV_XOXODAY / xoxoday.

GET /providers/{id}/vouchers Bearer

List Xoxoday Plum rewards (maps to getVouchers).

Pass reward id as fields.product_id on POST /quotes for the matching FEX product. denomination is always the SKU face value in currency (e.g. USD 10). Quote amount_payable is always GHS (converted + admin markup when face currency ≠ GHS); quote responses may include an fx block with the locked rate. Voucher rows may include ghs_* helpers; fx_status=unavailable means Admin has not set that FX rate. Quote error fx_rate_unavailable if the rate is missing. Receiver contact accepts local Ghana mobiles (e.g. 0245885885); FEX normalizes to 233… for Plum. Catalog is Ghana-only.

Path parameters

id required
string
Provider public id or code — PRV_XOXODAY / xoxoday.

Query parameters

product_code optional
string
FEX product code (xo-gift-voucher, xo-experience, xo-physical-voucher) — applies that product’s Plum product_category filter.
product_category optional
string
Plum type: e-gift voucher, Experience, or Physical voucher.
country optional
string
Ghana only. Omit or pass ghana / gh. Other values return validation_failed.
product_name optional
string
Filter by reward name.
voucher_category optional
string
Industry category (Electronics, Food & Restaurant, etc.).
min_price optional
string
Minimum denomination filter.
max_price optional
string
Maximum denomination filter.
limit optional
integer
Page size (0 = provider default). Prefer 50+ with page for full brand discovery.
page optional
integer
Page number (use with limit; response includes count, total, has_more when available).
GET http://fex.mojo-pay.com/v1/providers/{id}/vouchers
Request
GET /providers/{id}/vouchers
Authorization: Bearer <token>
Response 200
{
  "object": "provider_voucher_list",
  "provider_id": "PRV_XOXODAY",
  "provider_code": "xoxoday",
  "product_category": "e-gift voucher",
  "product_code": "xo-gift-voucher",
  "country": "ghana",
  "limit": 50,
  "page": 1,
  "count": 2,
  "total": null,
  "has_more": false,
  "vouchers": [
    {
      "id": "54514",
      "name": "Apple Gift Card",
      "currency": "USD",
      "product_category": "e-gift voucher",
      "min_denomination": 10,
      "max_denomination": 500,
      "ghs_min_denomination": "155.00",
      "ghs_max_denomination": "7750.00",
      "ghs_rate": "15.5",
      "fx_markup_percent": "0",
      "fx_status": "ok",
      "country": "Ghana",
      "image_url": "https://…",
      "provider_details": {}
    }
  ]
}
GET /providers/{id}/voucher-filters Bearer

List Xoxoday filter metadata (maps to getFilters). Use filter_group_code=product_category for e-gift / Experience / Physical voucher.

Path parameters

id required
string
PRV_XOXODAY / xoxoday.
GET http://fex.mojo-pay.com/v1/providers/{id}/voucher-filters
Request
GET /providers/{id}/voucher-filters
Authorization: Bearer <token>
GET /providers/{id}/order-history Bearer

Xoxoday Plum order history for a date range.

Path parameters

id required
string
PRV_XOXODAY / xoxoday.

Query parameters

start_date required
string
Y-m-d
end_date required
string
Y-m-d
limit optional
integer
Default 1000
page optional
integer
Default 1
GET http://fex.mojo-pay.com/v1/providers/{id}/order-history
Request
GET /providers/{id}/order-history
Authorization: Bearer <token>
GET /providers/{id}/payment-report Bearer

Xoxoday Plum payment report for a date range.

Path parameters

id required
string
PRV_XOXODAY / xoxoday.

Query parameters

start_date required
string
Y-m-d
end_date required
string
Y-m-d
limit optional
integer
Default 1000
page optional
integer
Default 1
GET http://fex.mojo-pay.com/v1/providers/{id}/payment-report
Request
GET /providers/{id}/payment-report
Authorization: Bearer <token>

Rewards (MojoLife)

MojoLife gift cards (stored value, redeemable across MojoLife merchants) and vouchers (redeemable, including partially, at participating merchants) for Ghana. FEX products: ml-gift-card and ml-voucher (under gift_cards). List SKUs, quote with the SKU product_id and the receiver email, then create the order. SKUs are priced in GHS, so no FX applies. When the order completes, the code is returned in provider_details.voucher_code, both on the order and in the order.completed webhook. Deals (ml-deal, under deals) are event tickets, experiences and offers; the buyer pays MojoLife directly (see below). Provider id: PRV_MOJOLIFE / mojolife.

The code is the value. Show it only to the customer who paid, and never log it. FEX stores it encrypted.

Quote request (POST /quotes). For vouchers, the voucher is assigned to email, and the holder must present that email when redeeming. Gift cards also accept optional recipient_name and message.

Request
{
  "product_id": "PRD_ML_GIFT_CARD",
  "fields": {
    "product_id": "0c895be8-bc97-4489-9ad4-cffecc96295a",
    "email": "receiver@example.com",
    "recipient_name": "Ama",
    "message": "Happy birthday!"
  }
}

Completed order (GET /orders/{id} or the order.completed webhook)

Response 200
{
  "id": "ORD_…",
  "object": "order",
  "status": "completed",
  "currency": "GHS",
  "amount_payable": "25.00",
  "product_id": "PRD_ML_GIFT_CARD",
  "provider_reference": "9b1f…",
  "provider_details": {
    "voucher_code": "MLGC-7Q4K-P2ZD",
    "voucher_type": "gift_card",
    "voucher_value": "25.00",
    "voucher_currency": "GHS",
    "voucher_expires_at": "2027-09-26T18:00:00+00:00"
  }
}

Deals are paid on MojoLife. Quote PRD_ML_DEAL with the deal product_id (id or slug), buyer_name, email, optional phone and quantity (1–20). The quote is the live price × quantity. When you create the order, payment.method is provider_checkout and payment.url is the MojoLife checkout page: send the buyer there. After they pay, the order goes straight to completed (order.paid then order.completed) with the confirmation code in provider_details.voucher_code (voucher_type deal). Free deals complete as soon as the order is created. Unpaid checkouts fail with payment_expired after 24 hours, and these orders cannot be cancelled through FEX. payment_method collection is not supported.

Quote request
{
  "product_id": "PRD_ML_DEAL",
  "fields": {
    "product_id": "f1d4c2a0-…",
    "buyer_name": "Ama Mensah",
    "email": "ama@example.com",
    "phone": "0241234567",
    "quantity": 2
  }
}
GET /providers/{id}/vouchers Bearer

List MojoLife gift card and voucher SKUs FEX can sell, and deals on sale.

Pass id as fields.product_id when you quote the matching FEX product. The quote amount_payable is the SKU price in GHS, which can be lower than face_value on discounted SKUs.

Path parameters

id required
string
PRV_MOJOLIFE / mojolife.

Query parameters

product_code optional
string
ml-gift-card, ml-voucher or ml-deal. Omit to list all; each row has fex_product_code. Deal rows also carry price, is_free, remaining, starts_at, ends_at, event_at and venue.
GET http://fex.mojo-pay.com/v1/providers/{id}/vouchers
Request
GET /providers/{id}/vouchers
Authorization: Bearer <token>
Response 200
{
  "object": "provider_voucher_list",
  "provider_id": "PRV_MOJOLIFE",
  "provider_code": "mojolife",
  "product_code": "ml-gift-card",
  "country": "ghana",
  "count": 1,
  "has_more": false,
  "vouchers": [
    {
      "id": "0c895be8-bc97-4489-9ad4-cffecc96295a",
      "name": "MojoLife ₵25 Gift Card",
      "description": "Spend on lifestyle experiences across MojoLife.",
      "fex_product_code": "ml-gift-card",
      "currency": "GHS",
      "face_value": "25.00",
      "price": "25.00",
      "validity_days": 365,
      "seller": null,
      "country": "Ghana",
      "image_url": "https://life.mojolabx.com/images/native/gift-card.png"
    }
  ]
}

Quotes

A quote locks the price for the fields you send. Every field must satisfy the product input_schema.

POST /quotes Bearer

Price a product for the submitted fields. Quotes expire — create the order before expires_at.

The top-level product_id is the FEX product (xo-gift-voucher, xo-experience or xo-physical-voucher); fields.product_id is the reward id from the voucher list. denomination is the face value in the reward’s own currency.

Body parameters

product_id required
string
Product public id from the catalog.
customer_id optional
string
Optional FEX customer id — provider-specific fields are merged from linked profiles. For SecondsTax, api_user defaults to the customer’s email or phone (same identity used on provider-funds / provider-portfolios).
fields required
object
Must satisfy the product input_schema. Explicit fields override merged customer/profile values (e.g. fields.api_user).
POST http://fex.mojo-pay.com/v1/quotes
Request (e-gift voucher)
{
  "product_id": "PRD_XO_GIFT_VOUCHER",
  "fields": {
    "product_id": "54514",
    "denomination": 10,
    "quantity": 1,
    "email": "receiver@example.com",
    "contact": "0245885885"
  }
}
Response 201
{
  "id": "QTE_…",
  "object": "quote",
  "product_id": "PRD_XO_GIFT_VOUCHER",
  "currency": "GHS",
  "amount": "155.00",
  "amount_payable": "155.00",
  "fx": {
    "face_currency": "USD",
    "face_denomination": 10,
    "face_amount": 10,
    "quantity": 1,
    "rate_to_ghs": "15.5",
    "markup_percent": "0",
    "amount_ghs": "155.00"
  },
  "expires_at": "2026-08-30T15:00:00+00:00"
}
GET /quotes/{id} Bearer

Retrieve a quote owned by the authenticated partner.

Path parameters

id required
string
Quote public id.
GET http://fex.mojo-pay.com/v1/quotes/{id}
Request
GET /quotes/{id}
Authorization: Bearer <token>

Orders

Lifecycle: pending_payment → paid → fulfilling → completed (or failed / cancelled / expired). After payment FEX places the Xoxoday order and the voucher is delivered to the receiver’s email / phone. MojoLife codes are issued immediately and the order completes with provider_details.voucher_code. MojoLife deals complete once the buyer pays on the MojoLife checkout.

POST /orders Bearer

Create an order from a quote and start payment.

Open payment.url. Sandbox-tier partners use FEX hosted sandbox pay; live-tier partners use MojoPay Omni when configured. Then poll or wait for webhooks. Configure your webhook URL under Partner Portal → Webhooks.

Headers

Idempotency-Key required
UUID recommended. Same key returns the same order.

Body parameters

quote_id required
string
From POST /quotes.
partner_reference required
string
Your reconciliation key.
payment_method optional
string
checkout (default) or collection.
success_url optional
url
Return URL after payment UX.
cancel_url optional
url
Cancel / abandon URL.
customer optional
object
Optional customer details.
metadata optional
object
Arbitrary partner metadata.
POST http://fex.mojo-pay.com/v1/orders
Request
{
  "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" }
}
Response 201
{
  "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} Bearer

Retrieve an order by public id.

Path parameters

id required
string
Order public id.
GET http://fex.mojo-pay.com/v1/orders/{id}
Request
GET /orders/{id}
Authorization: Bearer <token>
GET /orders Bearer

List up to 50 most recent orders for the partner.

Query parameters

status optional
string
Filter by order status.
GET http://fex.mojo-pay.com/v1/orders
Request
GET /orders
Authorization: Bearer <token>
Response 200
{
  "object": "list",
  "data": [
    { "id": "ORD_…", "object": "order", "status": "completed" }
  ]
}
POST /orders/{id}/cancel Bearer

Cancel an order when still cancellable (typically before payment completes).

Path parameters

id required
string
Order public id.
POST http://fex.mojo-pay.com/v1/orders/{id}/cancel
Request
POST /orders/{id}/cancel
Authorization: Bearer <token>

Webhooks

Configure a webhook URL per API client under Partner Portal → API & webhooks. FEX signs every delivery with FEX-Signature (HMAC-SHA256).

Verify signature

Header format:

FEX-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>

Signed payload: {timestamp}.{raw_body} using your webhook_secret.

Event envelope
{
  "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"
    }
  }
}

Event types

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.

Errors

Failed responses use the standard envelope. Use error.code for handling and request_id when contacting support. Provider codes (provider_*) can also appear on a failed order as failure.code.

Error envelope

{
  "error": {
    "code": "validation_failed",
    "message": "Product fields failed validation.",
    "details": { "errors": { "amount": ["required"] } },
    "request_id": "req_…"
  }
}

Common codes

unauthorized not_found product_unavailable validation_failed quote_unavailable quote_expired invalid_state fx_rate_unavailable provider_not_found provider_error