Get Categories
Retrieve all game categories with input form schemas for your assigned catalogs.
Returns all game categories (games) accessible to your reseller account across all assigned catalogs. Each category includes:
form: The input fields your UI needs to collect from the end-user (e.g., User ID + Zone ID for Mobile Legends).product_count: Number of active product denominations available.catalogs: Which price catalogs this game belongs to.icon_url: Artwork for the game, ornull. Treat it as an opaque, absolute URL and always render a fallback: the host is not guaranteed, not every game has custom artwork yet, and the value can change when we replace an icon. Do not hotlink it into a cached layer keyed on the URL never changing.
Start Here
This is the best endpoint to call first. Use the slug from each category to filter products via GET /api/v1/products?category_slug=mobile-legends.
form is a helper, not the order payload
The form array describes what to collect from your customer and how to validate it: labels, placeholders, and regex patterns for your own checkout UI. It does not map field-by-field to POST /order. When you place the order you collapse everything into a single target string (plus an optional zone for server-based games). See Mapping Form Fields to target & zone.
Endpoint
Request Headers
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
X-Api-Key | String | Yes | Your API Key. |
X-Timestamp | String | Yes | Current Unix timestamp in ms. |
X-Signature | String | Yes | HMAC-SHA256 signature. For GET requests, sign the timestamp only: HMAC(timestamp, apiSecret). |
Response
{
"success": true,
"data": {
"categories": [
{
"id": "597a5742-ba09-4373-9183-13397bd08416",
"name": "Mobile Legends: Bang Bang",
"slug": "mobile-legends",
"type": "instant",
"icon_url": "https://cdn.algan.id/public-assets/b2b-games/mobile-legends.webp",
"product_count": 24,
"form": [
{
"key": "user_id",
"type": "text",
"role": "account_id",
"label": "User ID",
"order": 1,
"required": true,
"placeholder": "e.g. 12345678",
"validation": {
"pattern": "^[0-9]+$",
"message": "User ID harus berupa angka"
}
},
{
"key": "zone_id",
"type": "text",
"role": "server",
"label": "Zone ID",
"order": 2,
"required": true,
"placeholder": "e.g. 1234",
"validation": {
"pattern": "^[0-9]+$",
"message": "Zone ID harus berupa angka"
}
}
],
"catalogs": [
{ "id": "cat-uuid-1", "name": "Katalog Dasar" }
]
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Free Fire",
"slug": "free-fire",
"type": "instant",
"icon_url": "https://cdn.algan.id/public-assets/b2b-games/free-fire.webp",
"product_count": 18,
"form": [
{
"key": "user_id",
"type": "text",
"label": "Player ID",
"order": 1,
"required": true,
"placeholder": "e.g. 123456789",
"validation": {
"pattern": "^[0-9]+$",
"message": "Player ID harus berupa angka"
}
}
],
"catalogs": [
{ "id": "cat-uuid-1", "name": "Katalog Dasar" }
]
},
{
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"name": "Valorant PH",
"slug": "VALOPH",
"type": "instant",
"icon_url": "https://cdn.algan.id/public-assets/b2b-games/valorant.webp",
"product_count": 12,
"form": [
{
"key": "riot_id",
"type": "text",
"label": "Riot ID + Tagline",
"order": 1,
"required": true,
"placeholder": "e.g. VALOUSERNAME#1234",
"validation": {
"pattern": "^.+#.+$",
"message": "Format harus Username#Tagline"
}
}
],
"catalogs": [
{ "id": "cat-uuid-1", "name": "Katalog Dasar" }
]
},
{
"id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"name": "Steam Voucher PHP",
"slug": "STEAMPHP",
"type": "voucher",
"icon_url": "https://cdn.algan.id/public-assets/b2b-games/steam.webp",
"product_count": 6,
"form": [],
"catalogs": [
{ "id": "cat-uuid-1", "name": "Katalog Dasar" }
]
}
]
},
"meta": {
"total": 4
}
}Field Roles: mapping the form to your order
Every field in form carries a role that tells you exactly where its collected value goes in POST /api/v1/order; no guessing by field name:
Field Roles
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id | target | No | The account identifier. Send the collected value as the order `target`. |
server | zone | No | The game server/zone. Send the collected value as the order `zone`. |
(none / other) | - | No | Display/validation-only field, not part of order routing today. |
So for the Mobile Legends example above: collect user_id (role account_id) → send as target; collect zone_id (role server) → send as zone.
Dropdown fields
A field may be type: "select" with an options: [{ "value", "label" }] array (e.g. a server picker). Render the labels for your customer and send the chosen value, mapped by its role exactly like a text field. If a field has no role, it is informational only; do not put it in target/zone.
Type Values
The type field indicates how the product is delivered and what you put in target when ordering:
Game Types
| Parameter | Required | Description |
|---|---|---|
instant | No | Direct top-up to the game account. Put the account identifier (User ID / Player ID / Riot ID) in target, and the server in zone if the form has a zone field. |
voucher | No | Returns a voucher/serial code the customer redeems manually. No game account, so form is usually empty; build your own form and pass any reference (e.g. customer or reseller email) as target. |
direct | No | Direct delivery to a phone number or other identifier. Put that identifier in target. |
Voucher products
For type: "voucher" (e.g. Steam Voucher PHP) we have no field to collect, so form is empty. Create your own form on your side (e.g. a customer email field) and send any value you control as target; your customer’s email or even your own reseller email works as a placeholder. The redeem code comes back to you in the serial_number of the order status and callback, formatted as ALG-XXXXXXXXXX/REAL-VOUCHER-CODE. Split on the first / and give your customer everything after it.
Example cURL
API_KEY="algan_live_..."
API_SECRET="sk_live_..."
TIMESTAMP=$(python3 -c "import time; print(int(time.time() * 1000))")
SIGNATURE=$(echo -n "${TIMESTAMP}" | \
openssl dgst -sha256 -hmac "${API_SECRET}" | awk '{print $2}')
curl -s https://algan.id/api/v1/categories \
-H "X-Api-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Signature: ${SIGNATURE}"
Caching
Performance
Cache-Control: private, max-age=300, must-revalidate · Vary: X-Api-Key
Categories rarely change; safe to cache for 5 minutes inside your own application.
Do not cache this on a shared CDN
The response is scoped to your account: the category set follows the catalogs assigned to you, and each entry lists the catalogs it belongs to. It is marked private for that reason. If you put a shared proxy or CDN in front of this endpoint, key it on X-Api-Key, never serve one reseller’s response to another.