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, or null. 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

GET/api/v1/categories

Request Headers

Headers

ParameterTypeRequiredDescription
X-Api-KeyStringYesYour API Key.
X-TimestampStringYesCurrent Unix timestamp in ms.
X-SignatureStringYesHMAC-SHA256 signature. For GET requests, sign the timestamp only: HMAC(timestamp, apiSecret).

Response

200OK
json
{
"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

ParameterTypeRequiredDescription
account_idtargetNoThe account identifier. Send the collected value as the order `target`.
serverzoneNoThe game server/zone. Send the collected value as the order `zone`.
(none / other)-NoDisplay/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

ParameterRequiredDescription
instantNoDirect 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.
voucherNoReturns 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.
directNoDirect 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.

PreviousAuthenticationNextProducts