API v1

H2H IndoTopup API

H2H Partner API documentation: find products, create orders, and follow the results. All examples use fictional data; replace the SKU with one from your account catalog.

Base URLhttps://h2h.indotopup.com/api/v1

Quick start

  1. Sign up, open the Keys & Webhook menu in the dashboard, then create a test key (itp_test_…). Copy its value: it is shown only once.
  2. Test the connection with GET /account.
  3. Fetch the catalog via GET /catalog/products and pick a product code.
  4. Create an order with POST /orders using the test key. Once you are confident, switch your account to Production, register your server IP, and swap to the live key (itp_live_…).

Authentication & limits

One Bearer token per key: send the header Authorization: Bearer <key> with every request (except /lookups/games and /lookups/quota). The test key and live key are two different tokens; never put either in public browser code.

  • itp_test_… simulates orders and webhooks, no balance is deducted, and it works from any IP.
  • itp_live_… real orders. Only active when the account is in Production mode, and only accepts requests from the IPs/CIDRs you have registered (empty = all IPs).
  • The per-minute request limit follows your account level and is reported in the RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers. 429 responses include Retry-After. Catalog endpoints are limited to 30 requests/minute.
  • All responses are JSON. Times use ISO 8601 UTC, and monetary values are whole-number Rupiah.

Test mode

Orders with the test key complete within seconds with an SBX-… serial and send webhooks as usual. Targets ending in 9999 simulate a failure. Orders from the dashboard are always live.

Status, refunds & idempotency

  • Order status: queued → processing → succeeded or failed. HTTP 201 only means the order was accepted; wait for the webhook or fetch GET /orders/{id}.
  • reference must be unique per order. On a timeout or 5xx, resend the same request with the same reference: you receive the original order (HTTP 200 + Idempotent-Replay: true header), with no double deduction. The Idempotency-Key header is accepted as a substitute.
  • A failed order automatically returns all held funds to your balance. With max_price, the final cost never exceeds the limit and the remainder is refunded.

Webhook

Set the Webhook URL in the dashboard. We send a JSON POST when an API order finishes (order.succeeded or order.failed), and order.verification_required when an order needs a code. Orders from the dashboard do not send webhooks. Event envelope:

Verify the signature

The H2H-Signature header has the form t=<unix>,v1=<hex>. Compute HMAC-SHA256 of the string "<t>.<raw body>" with the webhook secret, compare using a constant-time comparison, and reject if t is older than 5 minutes. Reply with HTTP 2xx. On failure, we retry after 1 minute, 5 minutes, 15 minutes, 1 hour, and 6 hours. Deduplicate by event id.

Error

All errors take the form { "error": { "code": "…", "message": "…", "fields": { … } } }. Use error.code for program logic, not the message text.

HTTPerror.codeMeaning
400bad_requestBody is not valid JSON.
401unauthorized / invalid_keyThe Authorization: Bearer <key> header is missing or the key is unknown.
402insufficient_balanceInsufficient balance to hold the order cost (or a paid player lookup).
403live_not_enabled / ip_not_allowed / account_suspendedA live key was used while the account is not yet in Production, the IP is not registered, or the account is suspended.
404not_foundThe order, batch, product, or endpoint was not found or does not belong to you.
409conflictThe reference was reused with different content, the invoice is not yet available, or the verification action is stale.
422validation_failed / product_not_found / max_price_too_low / product_out_of_stockInvalid input. Per-field details are in error.fields.
429rate_limited / quota_exceededRequest limit or lookup quota exceeded. Wait according to the Retry-After header.
500server_errorAn error on our side. It is safe to retry with the same reference.
502provider_unavailableThe player lookup service is temporarily unavailable (no quota consumed).

Account

GET/api/v1/account

Account info

Profile, the key mode in use (test/live), balance, level, and limits. The fastest way to confirm your key and IP are correct.

Request example

Response example

200401403429

Catalog

GET/api/v1/catalog/brands

List brands

Brands that have products. Limited to 30 requests/minute across all catalog endpoints; store the catalog on your side and sync it periodically.

Query

NameTypeDescription
type stringstandard | fallback

Request example

Response example

200401403422429
GET/api/v1/catalog/products

List products

Products with prices for your account level and their attached input fields (fields). Fallback products (type=fallback) must use max_price when ordered.

Query

NameTypeDescription
brand stringBrand ID or slug.
type stringstandard | fallback
q stringSearch by product name.
limit integerNumber of items (1–200).
after stringCopy paging.next_after from the previous page.

Request example

Response example

200401403422429
GET/api/v1/catalog/products/{code}

Product detail

A single product by code.

Path

NameTypeDescription
code *stringProduct code, e.g. P12319.

Request example

Response example

200401403404429

Order

POST/api/v1/orders

Create order

Creates a single order. 201 means the order was accepted; check status for the outcome (queued → processing → succeeded / failed). reference is your idempotency key: if the response is uncertain, resend the same request and you will receive the original order (200 + Idempotent-Replay: true header), not a new one. The Idempotency-Key header is accepted in place of reference. The full max_price (if any) is held upfront; the remainder is refunded on success, and everything is refunded on failure.

Body (JSON)

NameTypeDescription
product *stringProduct code from the catalog (P… standard, F… fallback).
target.id *stringPlayer ID / destination number.
target.zone string|nullServer/zone if the product requires it (use the option value, not the label).
target.extra object|nullOther additional inputs; keys come from the product fields.
reference *stringYour own unique reference, max 64 characters.
max_price integer|nullFinal cost cap (Rupiah). Required for fallback products.

Request example

Response example

201200400401402403422429
POST/api/v1/orders

Create fallback order

Fallback products (F… codes) try the cheapest route first and switch routes on failure. Send max_price as the price cap; the final cost never exceeds it and the difference is refunded.

Body (JSON)

NameTypeDescription
product *stringFallback product code (F…).
target.id *stringPlayer ID.
target.zone string|nullServer/zone.
reference *stringUnique reference.
max_price *integerMaximum price cap (≥ current price).

Request example

Response example

201200400401402403422429
GET/api/v1/orders/{id}

Order detail

Retrieve an order by ID or reference. serial contains the voucher/serial code if any; failure is populated on failure (balance is refunded automatically).

Path

NameTypeDescription
id *stringOrder ID (ord_…) or your own reference.

Request example

Response example

200401403404429
GET/api/v1/orders

List orders

Newest orders first.

Query

NameTypeDescription
status stringqueued | processing | succeeded | failed
mode stringtest | live
from stringStart date/time (ISO 8601).
to stringEnd date/time.
limit integerNumber of items (1–200).
after stringCopy paging.next_after from the previous page.

Request example

Response example

200401403422429
POST/api/v1/orders/{id}/verification

Submit verification code

Some orders ask the buyer for a code: order.verification.state = "required" and can_submit = true. Submit the code here. 202 only means the code was accepted for processing, not that it is valid. Resending the same code returns the previous result; a different code is rejected with 409. Limited to 5 requests/minute per order.

Path

NameTypeDescription
id *stringOrder ID (ord_…) or your own reference.

Body (JSON)

NameTypeDescription
code *stringCode from the buyer, 1–128 characters.
action_id stringLatest verification.id.
version integerLatest verification.version.

Request example

Response example

202401403404409422429

Batch

POST/api/v1/batches

Create batch

One standard product, one destination, 1–100 sequential orders. The entire cost is deducted upfront and the price is locked. reference is unique; resending an identical request returns the original batch, while a different request with the same reference is rejected with 409.

Body (JSON)

NameTypeDescription
product *stringProduct code from the catalog (P… standard, F… fallback).
target.id *stringPlayer ID / destination number.
target.zone string|nullServer/zone if the product requires it (use the option value, not the label).
target.extra object|nullOther additional inputs; keys come from the product fields.
reference *stringUnique batch reference.
count *integerNumber of orders, 1–100.

Request example

Response example

201200401402403409422429
GET/api/v1/batches

List batches

Newest batches first.

Query

NameTypeDescription
status stringqueued | processing | completed | partially_failed | failed
from stringStart (ISO 8601).
to stringEnd.
limit integerNumber of items (1–200).
after stringCopy paging.next_after from the previous page.

Request example

Response example

200401403422429
GET/api/v1/batches/{id}

Batch progress

Status and progress counts.

Path

NameTypeDescription
id *stringBatch ID (bat_…) or your own reference.

Request example

Response example

200401403404429
GET/api/v1/batches/{id}/orders

Orders in a batch

Sequential child orders (sequence field), each with its own ID.

Path

NameTypeDescription
id *stringBatch ID (bat_…) or your own reference.

Query

NameTypeDescription
limit integerNumber of items (1–200).
after stringCopy paging.next_after from the previous page.

Request example

Response example

200401403404429
GET/api/v1/batches/{id}/invoice

Batch invoice (PDF)

A single PDF summarizing costs and refunds after all orders are complete. 409 if not yet complete or if it is a test batch. A successful response is an application/pdf file.

Path

NameTypeDescription
id *stringBatch ID (bat_…) or your own reference.

Request example

200401403404409429

Player Lookup

GET/api/v1/lookups/gamespublic

Games that can be looked up

List of game codes and zone requirements. Public, no key needed.

Request example

Response example

200429
GET/api/v1/lookups/quotapublic

Player lookup quota & rates

Daily free quota and additional rates. Public. Player lookup and purchase limit checks share the same quota; resets at 00:00 UTC.

Request example

Response example

200429
POST/api/v1/lookups/player

Look up player name

Validate the player ID before ordering. Both found and not-found results consume quota. 404 account does not exist, 402 insufficient balance for a paid lookup, 429 quota exhausted.

Body (JSON)

NameTypeDescription
game *stringGame code from /lookups/games.
id *stringPlayer account ID.
zone string|nullServer/zone if required.

Request example

Response example

200401402403404422429502
POST/api/v1/lookups/purchase-limit/mobile-legends

Mobile Legends purchase limit

Check whether the weekly pass/double diamond packages have reached the purchase limit. Uses the same quota as player lookup.

Body (JSON)

NameTypeDescription
id *stringAccount ID.
zone *stringZone.

Request example

Response example

200401402403404422429502

Webhook

POST/api/v1/webhooks/test

Send test webhook

Sends a signed webhook.test event to your Webhook URL so you can test your receiver without creating an order. Set your Webhook URL and create a webhook secret in the dashboard first.

Request example

Response example

200401403422429