Skip to content

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/catalog

Headers ​

HeaderDescriptionRequired
X-Api-KeyYour API keyYes
X-TimestampUnix timestamp in secondsYes
X-NonceUnique request identifierYes
X-SignatureHMAC-SHA256 signature (v1=...)Yes

See API Key Authentication for signing details.

Query Parameters ​

ParameterTypeRequiredDescription
product_codestringNoReturn only one product
is_activebooleanNoFilter by active status (default: true)
include_inactivebooleanNoInclude 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": {...} }
}
FieldTypeDescription
last_updatedstringISO 8601 timestamp of last catalog update
treeobjectHierarchical structure for UI navigation
productsobjectFlat 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": {...}
  }
}
FieldTypeDescription
codestringUnique product identifier
namestringDisplay name
notestring | nullOptional description
image_urlstringProduct logo URL
processing_timestringinstant, 24_hours, 3_days, link, or pin
is_activebooleanEffective status after reseller overlay
denominationstring | nullRaw denomination string from backend
denomination_currencystring | nullCurrency or unit code for the denomination (for example MYR, BDT, Diamonds)
denomination_unit_pricenumberMYR price per denomination unit. Standard MYR products return 1, while foreign-currency products can return values like 0.059 for BDT
fieldsarrayForm field definitions
fulfillmentobjectMapping to payment request
pricingobjectWholesale cost and adjustments
min_amountobject | nullCustom minimum amount override for pricing fields
max_amountobject | nullCustom 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"
  }
}
PropertyTypeDescription
idstringUnique identifier within product
typestringtext, number, select, or money
input_modestringKeyboard hint: text, tel, numeric, email, decimal
labelstringDisplay label
placeholderstringInput placeholder text
requiredbooleanWhether field is required
ordernumberDisplay order (ascending)
rolestringaccount, pricing, or none
validationobjectpattern (regex), message (error text), min/max (for money/number)
data_sourceobjectSelect field options source
currencystringFor 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 ​

TypeUse CaseExample Fields
textPhone, NRIC, account numbersinput_mode: "tel" for phones, "numeric" for NRICs
numberPlayer IDs, quantitiesNumeric keyboard
selectPlans, packages, billersOptions from /v2/options
moneyPayment amountsvalidation.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" }
    }
  }
}
PropertyDescription
typereference = static list, cache locally. dynamic = user-specific, fetch per request
depends_onField IDs that must be filled before fetching (dynamic only)
endpointAlways /options — call via Options API
paramsQuery 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 }
    }
  }
}
PropertyTypeDescription
from_fieldstringField ID to get value from
pathstringProperty or dot-path into the selected option object (for pricing selects, denomination)
omit_if_emptybooleanExclude 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
  }
}
FieldTypeDescription
unit_pricestringComputed unit price as a decimal string (e.g. "0.9710")
currencystringCurrency 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
discountobjectWholesale discount applied to this product
price_adjustmentobject | nullOptional reseller price adjustment
has_loss_riskbooleantrue when adjustment can produce a loss at some denomination

Discount ​

The discount object describes the wholesale discount for the product:

FieldTypeDescription
typestringpercentage or fixed
valuestringHuman-readable discount value (e.g. "2.9%", "0%", "30 cent")

Percentage discount — unit_price is the cost multiplier per RM 1 face value:

ExampleMeaningCost for RM 100
unit_price: "0.9710", value: "2.9%"2.9% discount off face value100 × 0.9710 = RM 97.10
unit_price: "1.0000", value: "0%"No discount100 × 1.0000 = RM 100.00

Fixed discount — unit_price is a negative fixed amount deducted per transaction:

ExampleMeaningCost for RM 100
unit_price: "-0.3000", value: "30 cent"RM 0.30 fixed discount per transaction100 - 0.30 = RM 99.70
unit_price: "-0.6000", value: "60 cent"RM 0.60 fixed discount per transaction100 - 0.60 = RM 99.40

Price Adjustment ​

Optional. If null, no adjustment is applied.

TypeCalculationExample (RM 100)
fixeduser_pays = price + value100 + 0.50 = RM 100.50
percentageuser_pays = price × value100 × 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": {}
}

Pine Labs API Documentation