KTP Details Extraction
curl --request POST \
--url https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form documentImage=@example-file \
--form trustFlowId=123 \
--form 'trustValidationId=<string>'import requests
url = "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction"
files = { "documentImage": ("example-file", open("example-file", "rb")) }
payload = {
"trustFlowId": "123",
"trustValidationId": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('documentImage', '<string>');
form.append('trustFlowId', '123');
form.append('trustValidationId', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Indonesia KTP details extraction processed successfully",
"data": {
"outcome": "extracted",
"fields": {
"nik": {
"value": "3275011601234567",
"confidence": 99.8
},
"name": {
"value": "IRWAN SHAH",
"confidence": 100
},
"placeOfBirth": {
"value": "JAKARTA",
"confidence": 100
},
"dateOfBirth": {
"value": "1981-06-16",
"confidence": 100
},
"gender": {
"value": "LAKI-LAKI",
"confidence": 99.9
},
"bloodType": {
"value": "AB",
"confidence": 100
},
"address": {
"street": {
"value": "JL.PLUMPANG B",
"confidence": 91.5
},
"rt": {
"value": "003",
"confidence": 100
},
"rw": {
"value": "004",
"confidence": 99.8
},
"village": {
"value": "RAWA BADAK SELATAN",
"confidence": 99.4
},
"district": {
"value": "KOJA",
"confidence": 100
}
},
"religion": {
"value": "ISLAM",
"confidence": 100
},
"maritalStatus": {
"value": "KAWIN",
"confidence": 89.8
},
"occupation": {
"value": "KARYAWAN SWASTA",
"confidence": 99.9
},
"citizenship": {
"value": "WNI",
"confidence": 82.1
},
"validUntil": {
"value": "SEUMUR HIDUP",
"confidence": 100
},
"province": {
"value": "DKI JAKARTA",
"confidence": 100
},
"city": {
"value": "JAKARTA UTARA",
"confidence": 97.5
},
"issuedDate": {
"value": "2023-01-30",
"confidence": 100
},
"signature": "{IMAGE_URL}",
"photo": "{IMAGE_URL}"
},
"missingFields": []
},
"verificationStatus": "VERIFIED",
"verificationStatusCode": 3,
"trustFlowId": 0,
"trustValidationId": "{your_trust_validation_id}",
"verificationType": "ktp_details_extraction",
"metadata": null,
"createdAt": "2026-10-06 04:17:24+0000"
}Indonesia
KTP Details Extraction
Perform OCR on an Indonesian KTP (national identity) card and return the extracted fields.
POST
/
api
/
v3
/
verifications
/
indonesia
/
ktp-details-extraction
KTP Details Extraction
curl --request POST \
--url https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form documentImage=@example-file \
--form trustFlowId=123 \
--form 'trustValidationId=<string>'import requests
url = "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction"
files = { "documentImage": ("example-file", open("example-file", "rb")) }
payload = {
"trustFlowId": "123",
"trustValidationId": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('documentImage', '<string>');
form.append('trustFlowId', '123');
form.append('trustValidationId', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://{environment-subdomain}.idmetagroup.com/api/v3/verifications/indonesia/ktp-details-extraction")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"documentImage\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustFlowId\"\r\n\r\n123\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"trustValidationId\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Indonesia KTP details extraction processed successfully",
"data": {
"outcome": "extracted",
"fields": {
"nik": {
"value": "3275011601234567",
"confidence": 99.8
},
"name": {
"value": "IRWAN SHAH",
"confidence": 100
},
"placeOfBirth": {
"value": "JAKARTA",
"confidence": 100
},
"dateOfBirth": {
"value": "1981-06-16",
"confidence": 100
},
"gender": {
"value": "LAKI-LAKI",
"confidence": 99.9
},
"bloodType": {
"value": "AB",
"confidence": 100
},
"address": {
"street": {
"value": "JL.PLUMPANG B",
"confidence": 91.5
},
"rt": {
"value": "003",
"confidence": 100
},
"rw": {
"value": "004",
"confidence": 99.8
},
"village": {
"value": "RAWA BADAK SELATAN",
"confidence": 99.4
},
"district": {
"value": "KOJA",
"confidence": 100
}
},
"religion": {
"value": "ISLAM",
"confidence": 100
},
"maritalStatus": {
"value": "KAWIN",
"confidence": 89.8
},
"occupation": {
"value": "KARYAWAN SWASTA",
"confidence": 99.9
},
"citizenship": {
"value": "WNI",
"confidence": 82.1
},
"validUntil": {
"value": "SEUMUR HIDUP",
"confidence": 100
},
"province": {
"value": "DKI JAKARTA",
"confidence": 100
},
"city": {
"value": "JAKARTA UTARA",
"confidence": 97.5
},
"issuedDate": {
"value": "2023-01-30",
"confidence": 100
},
"signature": "{IMAGE_URL}",
"photo": "{IMAGE_URL}"
},
"missingFields": []
},
"verificationStatus": "VERIFIED",
"verificationStatusCode": 3,
"trustFlowId": 0,
"trustValidationId": "{your_trust_validation_id}",
"verificationType": "ktp_details_extraction",
"metadata": null,
"createdAt": "2026-10-06 04:17:24+0000"
}Submit one image of the front of an Indonesian KTP as
documentImage, together with your trustFlowId and trustValidationId. The endpoint accepts JPG, JPEG, PNG, and PDF files up to 2 MB. The response returns every KTP field with its own confidence score, the cropped portrait and signature, and an overall data.outcome.
Image quality guide
The endpoint reads the KTP with OCR, so the quality of the image directly limits what it can extract. A sharp, evenly lit, uncropped image produces complete fields with high confidence. A blurred, dark, or partly covered image produces missing fields, low confidence scores, or values that were misread. A poor image does not usually make the request fail. The engine still returns an answer as HTTP200, and the call is billed. Check verificationStatus and the fields described below before you rely on any extracted value.
Success, status, and quality are separate
Read the response in three layers. Each layer answers a different question.| Layer | Where to look | What it tells you |
|---|---|---|
| HTTP and API result | HTTP status and success | Whether the engine produced an answer. 200 with success: true means it did. A 400 or 500 with a code means it did not, and the call is not billed. |
| Verification status | verificationStatus and verificationStatusCode | How the extraction was graded: VERIFIED (3), REVIEW_NEEDED (2), or REJECTED (1). All three are HTTP 200 and billed. |
| Image quality | data.fields.<field>.confidence, null values, and data.missingFields | How reliable each extracted value is. Quality is judged per field, not for the response as a whole. |
How the status is decided
data.outcome | verificationStatus | When it happens |
|---|---|---|
extracted | VERIFIED | Every field below was read. |
partial | REVIEW_NEEDED | The mandatory fields were read, but at least one of these could not be read: religion, marital status, occupation, citizenship, valid-until date, city, or issue date. |
rejected | REJECTED | At least one mandatory field could not be read: NIK, name, place of birth, date of birth, street, portrait (photo), or signature. errors.errorCode is mandatory_fields_unreadable. |
rejected | REJECTED | The image was not recognised as a KTP. errors.errorCode is wrong_document_type. |
data.missingFields names the fields behind a partial or rejected result. A field counts as unreadable when its value is null, or when its confidence is 1.0. A NIK that is not exactly 16 digits also counts as unreadable. The 1.0 confidence is a floor, not a real 1% reading. It means no usable score was available.
A very poor image of a real KTP can produce REJECTED with wrong_document_type. This happens when the engine cannot read enough of the card to recognise it as a KTP.
Recommended capture conditions
For guidance on capturing a clear, readable KTP image, see How to properly capture documents.Authorizations
Use Bearer {your_api_token} in the Authorization header.
Headers
Body
multipart/form-data

