---
title: "Check whether an image is an Indonesian KTP, and whether it is usable"
method: POST
path: "/v1/ai/documents/ktp-detection"
tags: ["documents:ktp-detection"]
---

# Check whether an image is an Indonesian KTP, and whether it is usable

`POST /v1/ai/documents/ktp-detection`

## Request

- Base URL: `https://api.intura.co` (the document declares no server; this is the origin it was published from)
- URL: `https://api.intura.co/v1/ai/documents/ktp-detection`
- Auth: none declared

## Request body

- object: The JSON form of the call. The multipart form takes the same fields as parts — `file`, `object`, and `thresholds` as a JSON object.
  - `image` string, nullable: The photograph, base64-encoded. A `data:` URL is accepted and its media type is read from the prefix. Bounded by MAX_UPLOAD_MB. Send this or `object`, never both.
  - `object` string, nullable: A photograph already in storage, named by the `object` key `POST /v1/platform/storage/uploads` returned. Upload the capture straight from the browser or the phone to the presigned URL and send the key here: the image never passes through this request, so a large photograph costs an upload rather than an inference timeout. The key must be inside your own namespace. Send this or `image`, never both.
  - `media_type` string, nullable: The image's media type when it is not carried by a `data:` prefix. One of: image/jpeg, image/jpg, image/png, image/webp.
  - `thresholds` object, nullable: Move the cuts for this one call. `accept` and `review` cut the document evidence; `quality_accept` and `quality_retry` cut the capture. Any subset is fine — whatever you leave out keeps Intura's default. `GET /info` lists all four with their ranges.

## Response `200`

Successful Response

- ModulesV1AiDocumentsKtpDetectionSchemasDetectResponse
  - `status` string: Always `success` on a 2xx
  - `code` integer: Mirrors the HTTP status code
  - `data` ModulesV1AiDocumentsKtpDetectionSchemasDetectResponseData, required: `data` for POST /v1/ai/documents/ktp-detection.
    - `label` string, required: `ktp` when the frame is one; `unreadable` when it could not be seen well enough to say; the name of another Indonesian document when one was recognised; `other` otherwise.
    - `score` number, required: The number this endpoint measured and decided on, 0..1, rounded to 4 dp — P(injection), P(scam), how strongly the frame reads as the card. Compare it against `thresholds` and you have `label`. This is the only number to threshold on; what it means on a given route is documented there.
    - `thresholds` object: The cuts this answer was decided by, after your saved settings and anything sent in `thresholds` on the request. Published on every response so a verdict can be compared with an older one: a score means nothing without the bar it was held to. Empty on an endpoint with nothing to tune.
    - `result` ModulesV1AiDocumentsKtpDetectionSchemasDetectResultData: `result` for a detection call.
      - `action` string, required: `accept` — right document, usable photo. `retry` — ask for another capture and show `quality.advice`. `reject` — this is not the document you asked for. This is the field to branch on.
      - `document` ModulesV1AiDocumentsKtpDetectionSchemasDocumentData, required: `result.document` — what the frame is, and what said so.
        - `matched_fields` string[], required: Names of the printed field labels found on the card — `nik`, `nama`, `alamat`, and so on. The values are in `result.extraction`.
        - `detected_other` string, nullable: The other Indonesian document this looks like, when it looks like one — `npwp`, `kartu_keluarga`, `sim`, `passport`, `stnk`, `bpjs`, `kartu_mahasiswa`, `kartu_anggota`. The full list for this deployment is at `GET /info` under `other_documents`. Worth showing a user: naming the card they uploaded is an instruction, 'not a KTP' is a dead end.
        - `number_present` boolean, nullable: Whether a 16-digit number of the NIK's shape was found. Null when no text was read.
        - `number_valid` boolean, nullable: Whether that number passes the NIK's internal format check — province in range, a plausible date of birth including the +40 offset for a female holder, a non-zero sequence. The number itself is never returned.
      - `extraction` ModulesV1AiDocumentsKtpDetectionSchemasExtractionData: `result.extraction` — what is printed on the card, three ways.
        - `fields` object, required: Every printed value, tidied: `nik`, `nama`, `tempat_lahir`, `tanggal_lahir`, `jenis_kelamin`, `golongan_darah`, `alamat`, `rt_rw`, `kelurahan_desa`, `kecamatan`, `kabupaten_kota`, `provinsi`, `agama`, `status_perkawinan`, `pekerjaan`, `kewarganegaraan`, `berlaku_hingga`. A field that was not read is null. Fields the card prints from a fixed list come back in the card's spelling, with misreads corrected: `jenis_kelamin` (`LAKI-LAKI`, `PEREMPUAN`), `golongan_darah` (`A`, `B`, `AB`, `O`, with any rhesus sign), `agama`, `status_perkawinan` (`BELUM KAWIN`, `KAWIN`, `CERAI HIDUP`, `CERAI MATI`), `kewarganegaraan` (`WNI`, `WNA`), `provinsi` and `berlaku_hingga` (`SEUMUR HIDUP` or an ISO date). A value that matches no entry is null rather than a guess.
        - `masked` object, required: The same keys with identifying values replaced by the placeholders `guardrails/pii-detection` uses — `[NATIONAL_ID]`, `[NAME]`, `[DATE_OF_BIRTH]`, `[ADDRESS]`. The copy to store or log.
        - `derived` object, nullable: what the NIK encodes: `kode_provinsi`, `kode_kabupaten_kota`, `kode_kecamatan`, `tanggal_lahir` and `jenis_kelamin` (a day of birth over 40 means female). Null unless the NIK passes its format check.
      - `quality` QualityData, required: `result.quality` — is the photograph good enough to work from?
        - `score` number, required: Weighted capture quality, 0..1.
        - `action` string, required: `accept`, `retry` or `reject`, from the capture score alone — independent of what the document turned out to be.
        - `metrics` object, required: Each measured metric normalised to 0..1: `sharpness`, `exposure`, `contrast`, `glare`, `resolution`, and `reading` when text was read. A metric that could not be measured is absent rather than zero, and the weights are renormalised over the rest.
        - `issues` string[], required: What is wrong with the capture, if anything.
        - `advice` string[], required: One line per issue, written for the person holding the phone rather than for a developer. These are the strings to show in a capture UI.
      - `image` ImageData, required: `result.image` — what arrived, and what was measured.
        - `media_type` string, required
        - `width` integer, required: Working width in pixels, after any downscale.
        - `height` integer, required: Working height in pixels, after any downscale.
        - `original_width` integer, required
        - `original_height` integer, required
        - `downscaled` boolean, required: True when the upload was reduced to the pipeline's working size. Quality is always measured on the working image, so two captures of one card at different megapixel counts score the same.

---

[API](https://skmtc.dev/intura/apis/intura-ai-api.md) - [All operations](https://skmtc.dev/intura/apis/intura-ai-api/llms.txt) - [OpenAPI document](https://skmtc.dev/intura/apis/intura-ai-api/revisions/7cb6af4c6390?raw)
