← Blog
💳

How Points, Quota, and Payments Work

Guide · 5 min read

Three pricing models on one platform. A short guide to picking the right one — and handling a 402 correctly.

API.MY.ID runs three pricing models side by side. Understanding the difference will save you money and, more importantly, prevent avoidable errors in production.

The three models

  • Free — holidays and prayer times. No key, no quota, forever.
  • Monthly quota — data endpoints (regions, schools, rates, IP). 5,000 free hits/month per key; subscribe for more.
  • Points (pay-per-use) — AI endpoints (OCR, CV, articles, background removal). Pay only for successful hits.

Points: 1 IDR = 1 point

The rate is deliberately trivial so you can do the math in your head. A Rp 100,000 top-up gives 100,000 points. A KTP scan costs 150 points — that is Rp 150 per scan, and Rp 100,000 covers about 666 of them.

POST /ocr/ktp            →   150 points
POST /ocr/sim            →   300 points
POST /cv/extract         →   300 points
POST /image/remove-bg    →   300 points
POST /articles/generate  → 1,300 points

Two guarantees that matter

The points system is designed so you never pay for something you did not get:

  • Points are deducted only on success. A request rejected for a bad parameter costs nothing.
  • Upstream failures are refunded. If the AI service behind an endpoint fails after points were deducted, they are returned automatically in the same transaction.

Handling 402 properly

When the balance is short, the endpoint returns 402 Payment Required — and what makes it useful is that the error states exactly how much was needed versus what you have.

{
  "status": "error",
  "code": 402,
  "message": "This endpoint costs 300 points per successful hit but your balance is 120.",
  "needed": 300,
  "balance": 120,
  "hint": "POST /billing/topup"
}

Because the payload is structured, you can turn it into an auto top-up instead of paging your on-call engineer:

async function callAI(path, body) {
  let res = await send(path, body);

  if (res.status === 402) {
    const err = await res.json();
    // refill enough for this job plus a buffer
    await topUp(Math.max(err.needed * 20, 100000));
    res = await send(path, body);           // retry once
  }
  return res.json();
}
💡 Watch your balance via GET /keys/me — the response carries points, this month's usage, and active subscriptions. Schedule a daily check and top up before the balance hits zero, not after.

The payment flow

Top-ups and subscriptions are processed through Midtrans. The flow has three steps, and the third happens without you waiting on it:

  • Your backend calls POST /billing/topup and receives a snap_token.
  • Your frontend opens the Midtrans payment popup with that token.
  • Once payment settles, Midtrans sends us a webhook — your balance is credited automatically, usually within seconds.

Note the balance is credited by the settlement webhook, not by the popup closing. Do not add points in the frontend when onSuccess fires; just re-fetch /keys/me to display the authoritative number from the server.

Which model to pick

If your usage is steady and predictable — a checkout form hitting the Regions API thousands of times a day — a subscription is cheaper and removes the risk of running dry. If your usage is spiky — KTP OCR only when a new user signs up — points make more sense: you pay nothing on a quiet day.

Ready to build?

Create a free account and get your API key — 5,000 free hits a month, and two endpoints that need no key at all.