Open API Documentation

PPOB integration for partner companies — check Poin, top up, and purchase products.

Download Postman Collection

Overview

The Open API lets partner companies integrate with the MBT PPOB platform. Every product purchase is funded by your company's Poin (prepaid balance), which you can top up and monitor through these endpoints.

This API is available to partner companies only

Base URL

https://tokoatlas.com/api/partner

Authentication

Authenticate every request with your API Key and Secret, sent as HTTP headers. You can find and regenerate these in your company dashboard under Poin Perusahaan → Kredensial Open API.

HeaderDescription
X-Api-KeyYour company API key.
X-Api-SecretYour company API secret.
curl https://tokoatlas.com/api/partner/saldo \
  -H "X-Api-Key: mbtk_xxxxxxxxxxxxxxxx" \
  -H "X-Api-Secret: mbts_xxxxxxxxxxxxxxxx"

Errors

Responses use standard HTTP status codes and a consistent JSON shape:

{
  "success": false,
  "message": "Invalid API credentials."
}
CodeMeaning
200 / 201Success.
401Missing or invalid API credentials.
402Insufficient Poin.
403Account not allowed to use the API (e.g. MBT).
404Resource not found.
422Validation error.

API Console

Test every endpoint directly from this page. Credentials are stored only in your browser and sent straight to the API (same origin) — they never pass through a third party. Pick an endpoint, fill in the fields, then Send Request.

These requests are real. Endpoints marked writes create/modify data — Buy Product really purchases and deducts Poin.

Check Poin

GET /saldo

Returns your company's current balance.

curl https://tokoatlas.com/api/partner/saldo \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": {
    "company": "Partner Co",
    "balance": 1500000
  }
}

Top Up Poin

POST /saldo/topup

Creates a bank-transfer deposit ticket that adds balance to your company's Poin once the transfer is received. Settlement is asynchronous — poll Topup Status below or listen for the topup.success webhook.

FieldTypeDescription
amount number required Amount to add. Minimum 10,000.
reference_code string required Your own reference for this topup. Must be unique — reusing a value returns 409 and does not create a second ticket. Echoed back in every response.
curl -X POST https://tokoatlas.com/api/partner/saldo/topup \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{"amount": 500000, "reference_code": "TOPUP-1001"}'
Response
{
  "success": true,
  "message": "Tiket deposit berhasil dibuat. Selesaikan transfer sebelum batas waktu.",
  "data": {
    "topup_code": "TOP2205260000001123",
    "reference_code": "TOPUP-1001",
    "amount": 500000,
    "actual_amount": 500328,
    "instruction_text": "TOPLINK: Mhn transfer Rp. 500.328 (HRS SAMA) ke ...",
    "parsed_instruction": { "account_holder": "...", "banks": [ ... ], "hours": "09.00 - 18.00" },
    "status": "awaiting_transfer",
    "expired_at": "2026-05-22T21:00:00+07:00"
  }
}
Response (409 — duplicate reference_code)
{
  "success": false,
  "message": "reference_code already used. It must be unique.",
  "data": {
    "reference_code": "TOPUP-1001",
    "topup_code": "TOP2205260000001123",
    "status": "awaiting_transfer"
  }
}

Topup Status

GET /saldo/topup/{code}

Returns the current status of a topup request. {code} accepts either our topup_code or your own reference_code.

curl https://tokoatlas.com/api/partner/saldo/topup/TOPUP-1001 \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": {
    "topup_code": "TOP2205260000001123",
    "reference_code": "TOPUP-1001",
    "amount": 500000,
    "actual_amount": 500328,
    "status": "success",
    "channel": "partner_api",
    "instruction_text": "TOPLINK: Mhn transfer Rp. 500.328 (HRS SAMA) ke ...",
    "parsed_instruction": { "account_holder": "...", "banks": [ ... ], "hours": "09.00 - 18.00" },
    "failure_reason": null,
    "paid_at": "2026-05-22T12:05:00+07:00",
    "expired_at": "2026-05-22T21:00:00+07:00",
    "created_at": "2026-05-22T12:00:00+07:00"
  }
}

Statuses: pending, awaiting_transfer, success, failed, expired. Returns 404 for an unknown code.

Topup History

GET /saldo/topup-history

Returns a paginated list of your company's topup requests, newest first.

QueryTypeDescription
statusstringoptionalFilter by status: pending, awaiting_transfer, success, failed, expired.
pagenumberoptionalPage number (default 1).
rowsnumberoptionalRows per page (default 20, max 100).
curl "https://tokoatlas.com/api/partner/saldo/topup-history?status=success&rows=20" \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": [
    {
      "topup_code": "TOP2205260000001123",
      "reference_code": "TOPUP-1001",
      "amount": 500000,
      "actual_amount": 500328,
      "status": "success",
      "channel": "partner_api",
      "paid_at": "2026-05-22T12:05:00+07:00",
      "expired_at": "2026-05-22T13:00:00+07:00",
      "created_at": "2026-05-22T12:00:00+07:00"
    }
  ],
  "pagination": { "page": 1, "rows": 20, "total": 1, "last_page": 1 }
}

Poin History

GET /saldo/history

Returns a paginated ledger of every change to your company's Poin (top-ups and PPOB purchases), newest first.

QueryTypeDescription
typestringoptionalFilter by mutation type: credit (top-up) or debit (purchase).
pagenumberoptionalPage number (default 1).
rowsnumberoptionalRows per page (default 20, max 100).
curl "https://tokoatlas.com/api/partner/saldo/history?type=debit&rows=20" \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": [
    {
      "type": "debit",
      "source": "ppob_transaction",
      "amount": 5900,
      "balance_before": 2000000,
      "balance_after": 1994100,
      "description": "PPOB Three Reguler 5.000 · Tujuan 0895347740321",
      "reference_type": "App\\Models\\Transaction",
      "reference_id": "2205260000001123",
      "created_at": "2026-05-22T12:10:00+07:00"
    }
  ],
  "pagination": { "page": 1, "rows": 20, "total": 1, "last_page": 1 }
}

List Products

GET /products

Lists available PPOB products with their selling price.

QueryTypeDescription
qstringoptionalSearch term. Matches any part of the product name or code, case-insensitive.
providerstringoptionalFilter by provider. Also a partial match by default — THREE returns both THREE DATA and THREE REGULER.
product_codestringoptionalLook up a single product by its exact code (case-insensitive). Takes precedence over q.
matchstringoptionalSet to exact to make q and provider exact matches instead of partial. q then has to equal the full product name or code.
pagenumberoptionalPage number (default 1).
rowsnumberoptionalRows per page (default 100).
Search runs against the catalog's own naming, which uses the operator's formal brand — searching q=tri returns nothing because Three products are named Three Reguler … under provider THREE REGULER. Use the provider values listed below rather than guessing.

Provider values

Prepaid creditData packageOther
TSEL REGULERTSEL DATATOKEN (PLN prepaid)
XL REGULERXL DATA
ISAT REGULERISAT DATA
THREE REGULERTHREE DATA
AXIS REGULERAXIS DATA
BYU REGULERBYU DATA
SMART DATA
# Partial search (default)
curl "https://tokoatlas.com/api/partner/products?q=Three%20Reguler&rows=20" \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."

# One provider only, nothing else
curl "https://tokoatlas.com/api/partner/products?provider=THREE%20REGULER&match=exact" \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."

# A single product by code
curl "https://tokoatlas.com/api/partner/products?product_code=ATH5" \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": [
    {
      "product_code": "ATH5",
      "name": "Three Reguler 5.000",
      "provider": "THREE REGULER",
      "price": 7000,
      "active": true,
      "disrupted": false
    }
  ],
  "pagination": { "more": false }
}
Only products that are active, undisrupted and priced for your account are returned, so an exact product_code that exists upstream can still come back empty when it is not sellable to you.

PLN Inquiry

POST /pln/inquiry

Resolves a PLN meter / customer id to the registered customer name and daya (power rating), before you buy a token. This call is synchronous: it waits for the biller and returns the result in the response. It does not deduct Poin and is not blocked by your balance.

FieldTypeDescription
target_number string required PLN meter number / customer id (IDPEL).
curl -X POST https://tokoatlas.com/api/partner/pln/inquiry \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{ "target_number": "512345678901" }'
Response (200 — found)
{
  "success": true,
  "data": {
    "inquiry_id": "pi9f2c1a...",
    "target_number": "512345678901",
    "customer_name": "JULI MARIA",
    "daya": "2200",
    "status": "found"
  }
}
Response (404 — not found)
{
  "success": false,
  "message": "Pelanggan tidak ditemukan.",
  "data": { "inquiry_id": "pi9f2c1a...", "status": "invalid" }
}
Response (504 — biller still processing)
{
  "success": false,
  "message": "Inquiry masih diproses. Silakan cek ulang dengan inquiry_id.",
  "data": { "inquiry_id": "pi9f2c1a...", "status": "pending" }
}
The request is held up to ~12 seconds while the biller responds. On a 504, keep the inquiry_id and poll PLN Inquiry Status below. Rate limit: 30 requests / minute per company (429 when exceeded). A successful lookup is reused for 120 seconds, so repeating the same meter within that window returns instantly.

PLN Inquiry Status

GET /pln/inquiry/{inquiry_id}

Fetches a (possibly late) PLN inquiry result by its inquiry_id.

curl https://tokoatlas.com/api/partner/pln/inquiry/pi9f2c1a... \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."

Returns 200 (found), 202 (still pending) or 404 (not found / unknown inquiry_id), with the same data shape as above.

Buy Product

POST /transactions

Purchases a PPOB product. The product's price is deducted from your Poin.

FieldTypeDescription
product_code string required Product code (from /products).
target_number string required Destination (phone number / meter id).
partner_reference string required Your own reference for this order. Must be unique — reusing a value returns 409 and does not create a second transaction. Echoed back in every response.
curl -X POST https://tokoatlas.com/api/partner/transactions \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{
    "product_code": "ATH5",
    "target_number": "0895347740321",
    "partner_reference": "ORDER-1001"
  }'
Response (201)
{
  "success": true,
  "message": "Transaction submitted.",
  "data": {
    "code": "2205260000001123",
    "partner_reference": "ORDER-1001",
    "product_code": "ATH5",
    "product_name": "Three Reguler 5.000",
    "target_number": "0895347740321",
    "amount": 5900,
    "payment_status": "PAID",
    "transaction_status": "PROCESS",
    "created_at": "2026-05-22T12:00:00+07:00",
    "balance": 1994100
  }
}
If your Poin is insufficient the API returns 402 with the required amount and your current balance — no transaction is created.
partner_reference must be unique. Reusing a value returns 409 with the code of the existing transaction — no second transaction is created and no Poin is charged.
Response (409)
{
  "success": false,
  "message": "partner_reference already used. It must be unique.",
  "data": {
    "partner_reference": "ORDER-1001",
    "code": "2205260000001123"
  }
}

Check Transaction Status

GET /transactions/{code}

Returns the current status of a transaction you created.

You do not have to poll this endpoint to learn the outcome: register a webhook and we push transaction.success / transaction.failed with these same fields as soon as the supplier reports back. Keep this endpoint as the fallback.

curl https://tokoatlas.com/api/partner/transactions/2205260000001123 \
  -H "X-Api-Key: ..." -H "X-Api-Secret: ..."
Response
{
  "success": true,
  "data": {
    "code": "2205260000001123",
    "partner_reference": "ORDER-1001",
    "product_code": "ATH5",
    "product_name": "Three Reguler 5.000",
    "target_number": "0895347740321",
    "amount": 5900,
    "payment_status": "PAID",
    "transaction_status": "PROCESS",
    "created_at": "2026-05-22T12:00:00+07:00"
  }
}
PLN prepaid (Token Listrik): the response also carries a token field. It is null while the order is processing; once the electricity token is issued, transaction_status becomes DONE and token holds the 20-digit token.
Response (PLN — token issued)
{
  "success": true,
  "data": {
    "code": "2205260000001124",
    "partner_reference": "ORDER-1002",
    "product_code": "APLN100",
    "product_name": "TOKEN PLN 100K",
    "target_number": "32201687475",
    "amount": 101760,
    "payment_status": "PAID",
    "transaction_status": "DONE",
    "created_at": "2026-05-22T12:00:00+07:00",
    "token": "0758-9685-0347-2723-8165"
  }
}

Webhooks

Register a webhook URL in your company dashboard under Poin Perusahaan → Webhook URL. We send a POST request to that URL whenever a relevant event happens — a Poin top-up settling, or a Pulsa/Data or PLN Token order reaching its final status.

Every request has the same envelope: the event name in event, the event-specific fields in data, and the server time in timestamp.

Events

EventWhen it fires
topup.successA Poin top-up has been received and credited.
topup.expiredA Poin top-up ticket expired or was cancelled before payment.
transaction.successA Pulsa/Data or PLN Token order was fulfilled by the supplier. For PLN the electricity token is included.
transaction.failedA Pulsa/Data or PLN Token order failed at the supplier. Refundable failures are reversed to the source of payment.

Payload — Poin top-up

{
  "event": "topup.success",
  "data": {
    "topup_code": "TOP2205260000001123",
    "reference_code": "TOPUP-1001",
    "amount": 500000,
    "credited_amount": 500328,
    "status": "success",
    "balance": 2000000
  },
  "timestamp": "2026-05-22T12:00:00+07:00"
}

Payload — Transaction (Pulsa / Data)

data carries the same fields as GET /transactions/{code}, so no follow-up call is needed to reconcile. Match the order against your own partner_reference — the value you sent when creating it.

FieldTypeDescription
transaction_codestringOur transaction code — the same value as code in the API responses.
partner_referencestringThe reference you supplied when creating the order. Unique per partner, so it is safe to use as your reconciliation key.
typestringProduct family, e.g. Pulsa, Data, pln.
product_codestringProduct purchased.
product_namestringHuman-readable product name.
target_numberstringDestination phone number or PLN meter id.
amountnumberPoin charged for the order.
payment_statusstringPayment side of the order — PAID once the Poin was deducted.
transaction_statusstringFulfilment status: success, failed, or DONE for a PLN order whose token has been issued.
statusstringLegacy field, kept for backward compatibility. Always success or failed, mirroring the event name.
created_atstringWhen the order was created (ISO 8601, +07:00).
tokenstring|nullPLN Token orders only. The 20-digit electricity token, null on failure.
{
  "event": "transaction.success",
  "data": {
    "transaction_code": "2507260000001589",
    "partner_reference": "ORDER-1001",
    "type": "Pulsa",
    "product_code": "ATH5",
    "product_name": "Three Reguler 5.000",
    "target_number": "089678920409",
    "amount": 5900,
    "payment_status": "PAID",
    "transaction_status": "success",
    "status": "success",
    "created_at": "2026-07-25T11:42:04+07:00"
  },
  "timestamp": "2026-07-25T11:42:11+07:00"
}

Payload — Transaction (PLN Token)

PLN orders add token and report transaction_status: "DONE" once the supplier issues the token — matching what GET /transactions/{code} returns.

{
  "event": "transaction.success",
  "data": {
    "transaction_code": "2205260000001124",
    "partner_reference": "ORDER-1002",
    "type": "pln",
    "product_code": "APLN100",
    "product_name": "TOKEN PLN 100K",
    "target_number": "32201687475",
    "amount": 101760,
    "payment_status": "PAID",
    "transaction_status": "DONE",
    "status": "success",
    "created_at": "2026-05-22T12:00:00+07:00",
    "token": "0758-9685-0347-2723-8165"
  },
  "timestamp": "2026-05-22T12:00:05+07:00"
}
status is kept for backward compatibility and always mirrors the event (success / failed). On a failure the payload has the same shape, with status and transaction_status set to failed and token null.
Treat the webhook as a shortcut, not the source of truth. Delivery is attempted once and is not retried, so an endpoint that is down or slow (over 5 seconds) misses the notification permanently. Keep GET /transactions/{code} as your fallback for any order still open on your side.
A transaction.* event fires only when the supplier reports a final result, so an order still being processed upstream produces no event at all. Handle events idempotently, keyed on transaction_code — a repeated supplier report delivers the event again.
payment_status describes the original payment and stays PAID even after a failed order is refunded. Use status / transaction_status to decide the outcome; a refundable failure returns the Poin automatically and shows up in GET /saldo/history.

Verifying the signature

Every request carries an X-Mbt-Signature header: an HMAC-SHA256 of the raw request body, keyed by your API Secret. The event name is also sent as X-Mbt-Event. Recompute the signature and compare to confirm the request came from us.

// PHP example
$payload   = file_get_contents('php://input');
$expected  = hash_hmac('sha256', $payload, $apiSecret);
$signature = $_SERVER['HTTP_X_MBT_SIGNATURE'] ?? '';

if (! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}
Respond with a 2xx status to acknowledge receipt. Non-2xx responses (or timeouts after 5 seconds) are logged on our side; delivery is best-effort and is not retried.
Beranda
Masuk