{
"grant_type": "client_credentials",
"client_id": "fex_…",
"client_secret": "…"
}
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 1200
}
Partner API Product guide
Mutual funds, equities and fixed income through SecondsTax, IC Securities and Black Star — customers, KYC, funds and holdings.
http://fex.mojo-pay.com/v1
Everything you need to sell investments 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.
POST /oauth/token.POST /customers.POST /customers/{id}/kyc/refresh until approved. IC Securities / Black Star: link the existing provider account with POST /customers/{id}/provider-profiles.GET /customers/{id}/provider-funds.customer_id so FEX merges the provider identity, then create an order and send the customer to payment.url.GET /customers/{id}/provider-portfolios.| Product | Name | Provider | Required fields | Lookup |
|---|---|---|---|---|
PRD_BS_DEPOSITbs-fund-deposit |
Black Star Fund Deposit | Black Star | client_id portfolio_id security_id amount |
— |
PRD_BS_SELLbs-sell-order |
Black Star Sell Order | Black Star | client_id portfolio_id security_uuid quantity |
— |
PRD_IC_FUNDic-fund-investment |
IC Securities Fund Investment | IC Securities | account_id amount |
— |
PRD_ST_REDEEMst-fund-redemption |
SecondsTax Fund Redemption | SecondsTax | api_user subscription_id amount |
— |
PRD_ST_SUBSCRIBEst-fund-subscription |
SecondsTax Fund Subscription | SecondsTax | api_user fund_id amount |
— |
PRD_IC_EQUITYic-equity-market-order |
IC Securities Equity Market Order | IC Securities | account_id isin transaction_type volume |
— |
PRD_IC_FIXED_INCOMEic-fixed-income-order |
IC Securities Fixed Income Order | IC Securities | account_id isin transaction_type volume |
— |
Orientation only — your credentials see a partner-scoped subset. Always call GET /catalog/products and read each input_schema rather than hardcoding this table.
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.
| 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
}
Register end customers once, submit provider KYC (SecondsTax), link pre-provisioned provider ids (IC Securities, Black Star), then pass customer_id on quotes so FEX merges the right fields. For SecondsTax (st-*), the investor must select a fund manager before KYC — send fund_manager_code as BLACK (BlackStar) or INVES (InvestCorp). After submit, poll POST /customers/{id}/kyc/refresh until kyc_status is approved (or rejected). The investor identity is the customer’s email or phone — FEX sends it as X-Api-User (also stored on the provider profile after KYC). Use GET /customers/{id}/provider-funds for the investable fund catalog and GET /customers/{id}/provider-portfolios for that investor’s holdings. Existing SecondsTax investors can skip KYC if the customer already has the same email/phone SecondsTax knows.
POST /customers
Authorization: Bearer <token>
GET /customers/{id}
Authorization: Bearer <token>
{
"provider_id": "PRV_SECONDSTAX",
"fund_manager_code": "BLACK",
"kyc": {
"generalDetails": {
"title": "Mr",
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"dateOfBirth": "1996-01-03",
"gender": "Male",
"maritalStatus": "Single",
"nationality": "Ghanaian",
"countryOfBirth": "Ghana",
"placeOfBirth": "Accra",
"mothersMaidenName": "Smith",
"tin": "1234567890"
},
"contactDetails": {
"phone": { "countryCode": "+233", "number": "0255361949" },
"email": "john@example.com",
"digitalAddress": "GA-123-4567",
"residentialAddress": "123 Main Street, Accra",
"emergencyContactName": "Jane Doe",
"emergencyContactPhone": "+233209876543",
"emergencyContactAddress": "456 Secondary Street, Accra",
"emergencyEmail": "jane.doe@example.com"
},
"employmentDetails": {
"employmentStatus": "Employed",
"profession": "Software Engineer",
"employerName": "Tech Corp Ltd",
"employerAddress": "789 Business Rd, Accra",
"employerPhone": "+233302123456",
"employerDigitalAddress": "GA-987-6543"
},
"identityDetails": {
"idName": "Ghana Card",
"idNumber": "GHA-1234567890",
"issueDate": "2015-05-01",
"expiryDate": "2030-05-01",
"placeOfIssue": "Accra"
},
"financialDetails": {
"riskTolerance": "Medium",
"sourceOfFunds": "Salary",
"csdNumber": "CSD123456"
},
"nextOfKin": {
"title": "Mrs",
"firstName": "Mary",
"middleName": "Ann",
"lastName": "Doe",
"relationship": "Sister",
"phone": { "countryCode": "+233", "number": "209112233" },
"residentialAddress": "321 Family Street, Kumasi",
"digitalAddress": "AK-321-7654",
"email": "mary.doe@example.com"
},
"referralCode": "DOLIPS39",
"image": {
"front": "data:image/jpeg;base64,…",
"back": "data:image/jpeg;base64,…",
"selfie": "data:image/jpeg;base64,…"
}
}
}
{
"provider_id": "PRV_SECONDSTAX"
}
{
"customer": { "id": "CUS_…", "provider_profiles": [{ "provider_code": "secondstax", "kyc_status": "approved" }] },
"provider_profile": {
"provider_id": "PRV_SECONDSTAX",
"provider_code": "secondstax",
"kyc_status": "approved",
"profile": {
"api_user": "john@example.com",
"fund_manager_code": "BLACK",
"provider_user_id": "ec8d9686-…",
"provider_kyc_status": "approved",
"organisation_kyc_status": "verified",
"kyc_refreshed_at": "2026-09-17T11:00:00+00:00"
}
}
}
POST /customers/{id}/provider-profiles
Authorization: Bearer <token>
GET /customers/{id}/provider-funds
Authorization: Bearer <token>
{
"object": "provider_fund_list",
"customer_id": "CUS_…",
"provider_id": "PRV_SECONDSTAX",
"provider_code": "secondstax",
"api_user": "0554990633",
"funds": [
{
"id": "4ba4c1db-2f79-4c3f-a232-b32556c48853",
"name": "Enhanced Equity Beta Fund",
"description": "An equity-focused fund managed by BlackStar targeting high-growth opportunities with a strategic emphasis on market beta exposure.",
"currency": "GHS",
"category": "Equity Funds",
"minimum_investment": 100,
"risk_level": "high"
},
{
"id": "3f7275ee-0a35-4875-a811-d494fb1298d3",
"name": "Slytherin",
"description": null,
"currency": "GHS",
"category": "Specialty",
"minimum_investment": 50,
"risk_level": "low"
},
{
"id": "59f41325-c156-4ce2-8bfd-9051039fb884",
"name": "Gryffindor",
"description": null,
"currency": "GHS",
"category": "Money Market",
"minimum_investment": 100,
"risk_level": "medium"
}
]
}
GET /customers/{id}/provider-portfolios
Authorization: Bearer <token>
{
"object": "provider_portfolio",
"customer_id": "CUS_…",
"provider_id": "PRV_SECONDSTAX",
"provider_code": "secondstax",
"api_user": "0554990633",
"summary": {
"total_value": "8180.25",
"holding_count": 7
},
"holdings": [
{
"subscription_id": "440b2eb0-…",
"fund_id": "4ba4c1db-…",
"fund_name": "Enhanced Equity Beta Fund",
"fund_description": "An equity-focused fund managed by BlackStar targeting high-growth opportunities with a strategic emphasis on market beta exposure.",
"fund_category": "Equity Funds",
"amount": "100.00",
"status": "active"
}
]
}
A quote locks the price for the fields you send. Every field must satisfy the product input_schema.
{
"product_id": "PRD_ST_SUBSCRIBE",
"customer_id": "CUS_…",
"fields": {
"fund_id": "4ba4c1db-2f79-4c3f-a232-b32556c48853",
"amount": 500
}
}
{
"id": "QTE_…",
"object": "quote",
"product_id": "PRD_ST_SUBSCRIBE",
"customer_id": "CUS_…",
"currency": "GHS",
"amount": "500.00",
"fex_fee_amount": "…",
"mojopay_fee_amount": "…",
"amount_payable": "…",
"input_fields": {
"api_user": "john@example.com",
"fund_id": "4ba4c1db-2f79-4c3f-a232-b32556c48853",
"amount": 500
},
"expires_at": "2026-08-30T15:00:00+00:00"
}
GET /quotes/{id}
Authorization: Bearer <token>
Lifecycle: pending_payment → paid → fulfilling → completed (or failed / cancelled / expired). After payment FEX places the subscription, redemption or trade with the provider. Provider references appear on the order once available.
{
"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>
Configure a webhook URL per API client under Partner Portal → API & webhooks. FEX signs every delivery with FEX-Signature (HMAC-SHA256).
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.
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": {
"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
provider_not_found
provider_error