← Blog
🪪

Building an e-KYC Flow with KTP OCR

Tutorial · 7 min read

From a raw ID photo to verified data — architecture, code, and the pitfalls that quietly break onboarding.

Identity verification is the front door of any fintech, marketplace, or rental product. A user photographs their ID card, and within seconds your system must know: who is this, did we read it confidently, and is it safe to proceed? This article walks that flow down to the code.

The shape of the flow

A healthy e-KYC flow has four stages, and three of them happen on your backend — not on the user's phone:

  • The client uploads the ID photo to your backend (not directly to a third-party API).
  • Your backend calls OCR to turn the image into structured fields.
  • Your backend validates the result: NIK format, field completeness, confidence level.
  • Data is stored; ambiguous cases go to a manual review queue.
💡 Why not call it straight from the phone? Your API key would leak. Anyone who unpacks the app can spend your quota and points. Always proxy through your backend.

Calling KTP OCR

The endpoint takes an image as multipart or base64 and returns structured JSON along with a confidence level:

curl -X POST "https://v1.api.my.id/ocr/ktp" \
  -H "Authorization: Bearer $API_KEY" \
  -F "image=@ktp.jpg"

The response carries every field printed on the card, plus a confidence value that should drive what you do next:

{
  "status": "success",
  "points_charged": 150,
  "data": {
    "nik": "3204xxxxxxxxxxxx",
    "nama": "BUDI SANTOSO",
    "tempat_lahir": "BANDUNG",
    "tanggal_lahir": "17-08-1990",
    "jenis_kelamin": "LAKI-LAKI",
    "alamat": "JL. MERDEKA NO. 1",
    "kecamatan": "COBLONG",
    "confidence": "high"
  }
}

Never trust raw OCR output

This is the step teams skip most often. OCR turns pixels into text; it does not guarantee that text makes sense. A blurry photo, a glare spot, or a bent card can yield a NIK with two digits transposed. Validation is not optional.

The NIK has structure you can check without calling any service: 16 digits, the first six encode the region (province, regency, district), the next six encode the birth date — with 40 added to the day for women.

function nikIsPlausible(nik, birthDate, gender) {
  if (!/^\d{16}$/.test(nik)) return false;

  let day = parseInt(nik.slice(6, 8), 10);
  const isFemale = day > 40;
  if (isFemale) day -= 40;

  const month = nik.slice(8, 10);
  const [d, m] = birthDate.split('-');   // "17-08-1990"

  // the birth date encoded in the NIK must match the printed one
  if (day !== parseInt(d, 10) || month !== m) return false;

  // gender must be consistent with the +40 rule
  return isFemale === (gender === 'PEREMPUAN');
}

This cross-check catches the most dangerous class of OCR error: a single transposed digit in the NIK will almost always break the birth-date match.

Cross-check the address against region data

The next step raises data quality sharply: confirm the district you read actually exists. The Regions API validates it and hands you the official code to store — far more useful than a free-text string.

curl "https://v1.api.my.id/wilayah/search?q=coblong" \
  -H "Authorization: Bearer $API_KEY"

# → { "code": "3273090", "name": "COBLONG", "level": "district" }

Store 3273090, not "Coblong". The code is stable, joinable, and saves you from three spellings of the same place.

The decision rule

Combine the signals above into one clear decision — and above all, keep a human path open for the grey zone:

  • Auto-approvehigh confidence, consistent NIK, valid district.
  • Manual reviewmedium confidence, or any single check fails.
  • Ask for a retakelow confidence, or required fields are null.
💡 A manual review queue is not a sign your system failed — it is the safety valve that lets you automate the rest with confidence. KYC products that try to automate 100% of cases usually end up rejecting legitimate users.

Cost and failure

One KTP scan costs 150 points (equivalent to Rp 150), and only on success. If the upstream call fails, points are refunded automatically — you never pay for an error. An empty balance returns 402 stating how much was needed versus your balance, which makes it easy to trigger an auto top-up.

Finally: treat ID photos as the most sensitive data in your system. Do not keep the raw image longer than the verification itself requires.

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.