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 |
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 active status (default: true) |
include_inactive | boolean | No | Include inactive products (default: false). Use when you need to retrieve all products regardless of active status |
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
Use the last_updated field for cache freshness checks. 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 X-API-Version is 2026-09-16 or later; default from 2027-01-11.
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.
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 (tree structure is preserved):
json
{
"last_updated": "2025-01-07T00:00:00Z",
"tree": { "groups": [...] },
"products": {}
}
