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-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) |
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": "price.amount" },
"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 | Dot-path into the selected option object (e.g. price.amount) |
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. The fulfillment path property accesses nested values like price.amount from this object.
The amount in the payment request is the transaction amount (from price.amount in options, or direct money input). See Product Examples for end-to-end flows.
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": {}
}