Appearance
Catalog API
Returns the complete product catalog with field configurations and fulfillment mapping. Designed for device caching — fetch once, render dynamically.
Migrating from /v2/product-list?
See the Migration Guide for endpoint mapping and a checklist.
Endpoint
http
GET https://api.iimmpact.com/v2/catalogHeaders
| Header | Description | Required |
|---|---|---|
X-Api-Key | Your API key | Yes |
X-Timestamp | Unix timestamp in seconds | Yes |
X-Nonce | Unique request identifier | Yes |
X-Signature | HMAC-SHA256 signature (v1=...) | Yes |
X-API-Version | Contract version date, e.g. 2026-09-16 (see Versioning) | No |
See API Key Authentication for signing details.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product_code | string | No | Return only one product |
is_active | boolean | No | Filter by your account's effective active status (default: true). false returns only products you have disabled |
include_inactive | boolean | No | When true, ignores is_active and also returns products you have disabled, with is_active: false (default: false) |
Products that are globally inactive, or that your account is restricted from purchasing, are never listed, regardless of these parameters. Hidden products are not listed either, except Fund Disbursement (FD), which is listed for accounts with Fund Disbursement access.
Example Request
bash
curl -X GET "https://api.iimmpact.com/v2/catalog" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "X-Timestamp: 1704067200" \
-H "X-Nonce: req-1704067200-a1b2c3d4e5f6" \
-H "X-API-Version: 2026-09-16" \
-H "X-Signature: v1=BASE64_HMAC_SIGNATURE"Response
json
{
"last_updated": "2025-01-07T00:00:00Z",
"tree": { "groups": [...] },
"products": { "D": {...}, "M": {...} }
}| Field | Type | Description |
|---|---|---|
last_updated | string | ISO 8601 timestamp of last catalog update |
tree | object | Hierarchical structure for UI navigation |
products | object | Flat map of products keyed by product_code |
Cache Strategy
last_updated tracks changes to groups, categories, products and your customisations. It does not move when options or wholesale pricing change, so also refresh periodically, together with the relevant /v2/options pages (and on catalog webhooks). This endpoint does not guarantee ETag or Last-Modified headers.
Tree
Navigation hierarchy: group → category → product codes.
json
{
"groups": [
{
"id": "grp_mobile",
"name": "Mobile",
"icon_url": "https://cdn.iimmpact.com/icons/mobile.png",
"categories": [
{
"id": "cat_prepaid",
"name": "Prepaid Reload",
"product_codes": ["D", "M", "C", "U"]
}
]
}
]
}The tree controls UI ordering; products is a flat map for O(1) lookup.
Product
Each entry in the products map:
json
{
"D": {
"code": "D",
"name": "Digi Prepaid",
"note": "Prepaid reload for Digi mobile numbers",
"image_url": "https://dashboard.iimmpact.com/img/D.png",
"processing_time": "instant",
"is_active": true,
"denomination": "5,10,30,50,100",
"denomination_currency": "MYR",
"denomination_unit_price": 1.0,
"fields": [...],
"fulfillment": {...},
"pricing": {...}
}
}| Field | Type | Description |
|---|---|---|
code | string | Unique product identifier |
name | string | Display name |
note | string | null | Optional description |
image_url | string | Product logo URL |
processing_time | string | instant, 24_hours, 3_days, link, or pin |
is_active | boolean | Effective status after reseller overlay |
denomination | string | null | Raw denomination string from backend |
denomination_currency | string | null | Currency or unit code for the denomination (for example MYR, BDT, Diamonds) |
denomination_unit_price | number | MYR price per denomination unit. Standard MYR products return 1, while foreign-currency products can return values like 0.059 for BDT |
fields | array | Form field definitions |
fulfillment | object | Mapping to payment request |
pricing | object | Wholesale cost and adjustments |
min_amount | object | null | Custom minimum amount override for pricing fields |
max_amount | object | null | Custom maximum amount override for pricing fields |
Display processing_time to users before checkout: instant = seconds, 24_hours = 1 business day, 3_days = 3 business days, link = fulfilled via a redemption link, pin = fulfilled as a PIN/voucher code.
For international topup and other non-MYR denomination products, multiply the displayed denomination by denomination_unit_price to derive the MYR face value before any reseller price adjustment.
Fields
Each field in the fields array defines a form input:
json
{
"id": "phone",
"type": "text",
"input_mode": "tel",
"label": "Phone Number",
"placeholder": "e.g. 0123456789",
"required": true,
"order": 1,
"role": "account",
"validation": {
"pattern": "^01[0-9]{8,9}$",
"message": "Enter valid Malaysian phone number"
}
}| Property | Type | Description |
|---|---|---|
id | string | Unique identifier within product |
type | string | text, number, select, or money |
input_mode | string | Keyboard hint: text, tel, numeric, email, decimal |
label | string | Display label |
placeholder | string | Input placeholder text |
required | boolean | Whether field is required |
order | number | Display order (ascending) |
role | string | account, pricing, or none |
validation | object | pattern (regex), message (error text), min/max (for money/number) |
data_source | object | Select field options source |
currency | string | For money fields only (default: MYR) |
A product may omit an account field from its catalog fields, for example for PIN products. The account field remains required when calling POST /v2/topup.
Field Types
| Type | Use Case | Example Fields |
|---|---|---|
text | Phone, NRIC, account numbers | input_mode: "tel" for phones, "numeric" for NRICs |
number | Player IDs, quantities | Numeric keyboard |
select | Plans, packages, billers | Options from /v2/options |
money | Payment amounts | validation.min/max for range, input_mode: "decimal" |
Select Fields
Select fields declare how to fetch their options via data_source:
json
{
"data_source": {
"type": "dynamic",
"depends_on": ["phone"],
"endpoint": "/options",
"params": {
"product_code": { "static": "HI" },
"field_id": { "static": "plan" },
"account_number": { "from_field": "phone" }
}
}
}| Property | Description |
|---|---|
type | reference = static list, cache locally. dynamic = user-specific, fetch per request |
depends_on | Field IDs that must be filled before fetching (dynamic only) |
endpoint | Always /options — call via Options API |
params | Query params: { "static": "value" } for constants, { "from_field": "id" } for user input |
See Product Examples for complete select field flows.
Fulfillment
Maps form fields to the POST /v2/topup payment request. The product field is always the product's code. You provide your own refid and optional remarks.
json
{
"fulfillment": {
"account": { "from_field": "phone" },
"amount": { "from_field": "plan", "path": "denomination" },
"extras": {
"subproduct_code": { "from_field": "plan", "path": "code" },
"ref2": { "from_field": "ref2", "omit_if_empty": true }
}
}
}| Property | Type | Description |
|---|---|---|
from_field | string | Field ID to get value from |
path | string | Property or dot-path into the selected option object (for pricing selects, denomination) |
omit_if_empty | boolean | Exclude from request if value is empty |
Store the Full Option Object
For select fields, store the entire selected item from /v2/options — not just the code. Fulfillment reads denomination for the transaction amount and may read code for extras.subproduct_code.
For a pricing select field, amount is the option's face value or quantity from denomination, not the adjusted customer-facing price.amount. When a product supports extras, its fulfillment includes extras.subproduct_code mapped from the selected option's stable code. Products that do not support extras do not gain that mapping. Direct money fields continue to map their entered value without a path.
Returned when you send X-API-Version: 2026-09-16; default from 2027-01-11. See Versioning.
The option code identifies the selected plan or package, while denomination is the face value or quantity sent for fulfillment. price.amount is the adjusted customer-facing selling price, and cost.amount is the reseller's wholesale cost.
Versioning
X-API-Version takes a date (YYYY-MM-DD). Dates later than tomorrow (UTC) and malformed values are ignored, and the current default contract is used.
| Release | Change | Default for all requests |
|---|---|---|
2026-09-16 | Options expose denomination; catalog amount fulfillment maps denomination and extras.subproduct_code | 2027-01-11 |
Until the default date, send X-API-Version: 2026-09-16 on GET /v2/catalog and GET /v2/options to receive this contract.
Pricing
B2B Data
Pricing is for your backend only. Do not expose unit_price, discount, or price_adjustment to end users.
json
{
"pricing": {
"unit_price": "0.9710",
"currency": "MYR",
"discount": {
"type": "percentage",
"value": "2.9%"
},
"price_adjustment": null,
"has_loss_risk": false
}
}| Field | Type | Description |
|---|---|---|
unit_price | string | Computed unit price as a decimal string (e.g. "0.9710") |
currency | string | Currency or unit code. Fiat currencies (e.g. MYR, IDR, BDT) for standard products, or game/virtual currencies (e.g. UC, Diamonds) for game top-up products |
discount | object | Wholesale discount applied to this product |
price_adjustment | object | null | Optional reseller price adjustment |
has_loss_risk | boolean | true when adjustment can produce a loss at some denomination |
Discount
The discount object describes the wholesale discount for the product:
| Field | Type | Description |
|---|---|---|
type | string | percentage or fixed |
value | string | Human-readable discount value (e.g. "2.9%", "0%", "30 cent") |
Percentage discount — unit_price is the cost multiplier per RM 1 face value:
| Example | Meaning | Cost for RM 100 |
|---|---|---|
unit_price: "0.9710", value: "2.9%" | 2.9% discount off face value | 100 × 0.9710 = RM 97.10 |
unit_price: "1.0000", value: "0%" | No discount | 100 × 1.0000 = RM 100.00 |
Fixed discount — unit_price is a negative fixed amount deducted per transaction:
| Example | Meaning | Cost for RM 100 |
|---|---|---|
unit_price: "-0.3000", value: "30 cent" | RM 0.30 fixed discount per transaction | 100 - 0.30 = RM 99.70 |
unit_price: "-0.6000", value: "60 cent" | RM 0.60 fixed discount per transaction | 100 - 0.60 = RM 99.40 |
Price Adjustment
Optional. If null, no adjustment is applied.
| Type | Calculation | Example (RM 100) |
|---|---|---|
fixed | user_pays = price + value | 100 + 0.50 = RM 100.50 |
percentage | user_pays = price × value | 100 × 1.03 = RM 103.00 |
See the Pricing Guide for margin strategies and the Product Examples for worked calculations.
Error Responses
400 — Validation Error
json
{
"message": "The given data was invalid.",
"errors": {
"is_active": ["The value 'yes' is not valid."]
}
}401 — Authentication Failed
json
{
"message": "Unauthorized",
"metadata": { "status_code": "401" }
}See API Key Authentication for the complete list of 401 causes.
Product Code No Match
Returns 200 OK with an empty products map. Groups and categories that contain no matching products are removed from the tree, so the tree is empty too:
json
{
"last_updated": "2025-01-07T00:00:00Z",
"tree": { "groups": [] },
"products": {}
}