> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Ekstraksi Data Dokumen Kustom dengan API Document AI

> Didit mengekstrak kolom kustom dari PDF atau gambar, server ke server, dengan cek nama dan manipulasi. POST /v3/document-ai/. $0.20 per dokumen.

Kirim satu dokumen beserta daftar kolom yang ingin dibaca darinya. Didit menjalankan model ekstraksi default pada file tersebut, mengembalikan setiap kolom dengan tipenya (`text`, `number`, atau `date` dalam format `YYYY-MM-DD`), mendeteksi dan mendekode kode QR dan barcode yang didukung, memeriksa file dari kemungkinan manipulasi, dan secara opsional membandingkan nama pada dokumen dengan orang atau perusahaan yang Anda harapkan. Gunakan untuk bukti dana, slip gaji, rekening koran, dokumen pajak, faktur, atau dokumen apa pun yang tidak mengikuti templat tetap.

Ini adalah versi server-ke-server dari langkah [Document AI](https://docs.didit.me/id/core-technology/document-ai/overview.md) yang di-hosting. Permintaannya berformat `multipart/form-data`: file dikirim di `document`, dan definisi kolom dikirim di `fields` sebagai satu string yang berisi array JSON.

## Tentukan kolom yang akan diekstrak

`fields` adalah array JSON berisi 1 hingga 30 item, dikirim sebagai satu string. Setiap item memiliki `key` (huruf kecil, angka, dan garis bawah, unik dalam satu permintaan), `name` yang mudah dibaca, `instruction` yang memberi tahu model apa yang harus dicari, `type` (`text`, `number`, atau `date`), serta dua flag: `required` dan `is_full_name`.

```json theme={null}
[
  {
    "key": "account_holder",
    "name": "Account holder",
    "instruction": "Full name of the person or company the account belongs to",
    "type": "text",
    "required": true,
    "is_full_name": true
  },
  {
    "key": "balance",
    "name": "Closing balance",
    "instruction": "Closing balance shown on the statement, as a number without the currency symbol",
    "type": "number",
    "required": true
  },
  {
    "key": "currency",
    "name": "Currency",
    "instruction": "Three-letter currency code of the balance",
    "type": "text"
  },
  {
    "key": "statement_date",
    "name": "Statement date",
    "instruction": "Date the statement was issued",
    "type": "date",
    "required": true
  }
]
```

Dalam permintaan multipart, seluruh array dikirim sebagai satu nilai formulir:

```bash theme={null}
curl -X POST https://verification.didit.me/v3/document-ai/ \
  -H "x-api-key: $DIDIT_API_KEY" \
  -F "document=@./bank-statement.pdf" \
  -F 'fields=[{"key":"account_holder","name":"Account holder","instruction":"Full name of the account holder","type":"text","required":true,"is_full_name":true},{"key":"balance","name":"Closing balance","instruction":"Closing balance as a number","type":"number","required":true}]' \
  -F "expected_first_name=Jane" \
  -F "expected_last_name=Doe"
```

Respons mengembalikan `fields` yang sudah dinormalisasi dan `extracted_data` dengan kunci berupa nilai `key` Anda, dengan `null` untuk setiap nilai yang tidak dapat ditemukan oleh model. `name`, `instruction`, dan `type` bersifat opsional pada setiap item (`type` default-nya `text`); `key` wajib diisi. Jika `fields` dikirim sebagai kunci formulir gaya bracket (`fields[0][key]=...`) atau dalam format selain array JSON, respons `400` dikembalikan sebelum ekstraksi berjalan, sehingga tidak ada biaya yang ditagihkan.

## Kode QR dan barcode

Setiap respons yang berhasil menyertakan `document_ai.detected_codes`. Setiap item berisi:

| Kolom | Deskripsi |
| - | - |
| `type` | Simbologi yang terdeteksi, seperti `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, atau `PDF_417` |
| `page_number` | Nomor halaman, dimulai dari 1 |
| `position` | Poligon yang terdeteksi dalam piksel halaman yang dirender, atau `null` jika lokasi yang andal tidak tersedia |
| `x`, `y`, `width`, `height` | Kotak pembatas dalam piksel halaman yang dirender, atau `null` jika lokasi yang andal tidak tersedia |
| `page_width`, `page_height` | Dimensi halaman yang dirender dan digunakan oleh koordinat |
| `parsed_payload` | String atau nilai terstruktur hasil dekode, atau `null` jika hanya lokasi kode yang dapat ditentukan |
| `raw_payload` | Payload mentah dari decoder sebagai string base64 bila tersedia; jika tidak, string kosong |

Array ini mencakup setiap halaman yang diproses dari sebuah PDF dan kosong jika tidak ditemukan kode yang didukung. Didit tidak pernah menebak payload: kode QR yang rusak dapat dikembalikan dengan lokasinya dan `parsed_payload: null`, sementara kegagalan dalam deteksi kode tidak menggagalkan permintaan Document AI.

## Pencocokan nama

Tandai paling banyak satu kolom dengan `is_full_name: true` dan kirim nama orang atau perusahaan yang Anda harapkan memiliki dokumen tersebut:

| Yang Anda verifikasi | Kirim | Nama dibandingkan dengan |
| - | - | - |
| Orang | `expected_first_name` + `expected_last_name` | nama lengkap orang tersebut |
| Perusahaan | `expected_company_name` (+ opsional `expected_company_country`, kode ISO 2 huruf) | nama perusahaan |

Kirim salah satu kelompok saja, jangan keduanya. Ketika sebuah nama berhasil diekstrak dan nama yang diharapkan tersedia, respons menyertakan `name_match_score` (0 hingga 100) dan `name_on_document`. Skor di bawah `document_ai_name_match_score_threshold` (default `80`) memicu peringatan `DOCUMENT_AI_NAME_MISMATCH` dan menerapkan `document_ai_name_mismatch_action`. Jika tidak ada kolom yang ditandai `is_full_name`, Didit akan menggunakan kolom teks pertama yang kuncinya menyerupai nama (`name`, `holder`, `full_name`, `account_holder`); tandai kolom secara eksplisit jika Anda ingin mengontrol nilai mana yang dibandingkan. Jika tidak ada nilai nama yang dapat ditemukan pada dokumen, atau tidak ada nama yang diharapkan dikirim, pemeriksaan nama dilewati dan `name_match_score` bernilai `null`.

## Risiko dan tindakan

Setiap risiko di bawah ini dipetakan ke parameter tindakan yang dapat Anda atur per permintaan: `DECLINE`, `REVIEW`, atau `NO_ACTION`. Tindakan yang paling ketat dari yang terpicu menentukan `document_ai.status` (`Declined` > `In Review` > `Approved`), dan setiap risiko yang terpicu dicantumkan di `document_ai.warnings`. Default-nya sama dengan langkah alur kerja yang di-hosting; lihat [Aturan & peringatan Document AI](https://docs.didit.me/id/core-technology/document-ai/warnings-document-ai.md) untuk mengetahui cara setiap risiko dideteksi.

| Risiko | Parameter | Default |
| - | - | - |
| `DOCUMENT_AI_UNREADABLE_DOCUMENT` | `document_ai_unreadable_document_action` | `REVIEW` |
| `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` | `document_ai_missing_required_fields_action` | `REVIEW` |
| `DOCUMENT_AI_SUSPECTED_MANIPULATION` | `document_ai_document_tampering_action` | `DECLINE` |
| `DOCUMENT_AI_NAME_MISMATCH` | `document_ai_name_mismatch_action` | `NO_ACTION` |
| `DOCUMENT_AI_UNSUPPORTED_FILE` | `document_ai_unsupported_file_action` | `REVIEW` |

## File, penyimpanan, dan penagihan

* **File yang diterima:** PDF (multi-halaman didukung), JPG, JPEG, PNG, TIFF, WebP, atau ZIP yang berisi dokumen, hingga 30 MB. PDF yang dienkripsi akan menghasilkan respons `400`; unggah PDF yang sudah didekripsi atau foto sebagai gantinya.
* **`save_api_request`** (default `true`): permintaan, kolom hasil ekstraksi, dan kode yang terdeteksi akan muncul di Business Console pada **Pemeriksaan Manual**, tempat tim Anda dapat meninjaunya. Atur ke `false` untuk melewati penyimpanan.
* **`vendor_data`** dan **`metadata`** dikembalikan tanpa perubahan dalam respons agar Anda dapat menautkan hasil dengan catatan Anda sendiri.
* **Harga:** \$0.20 per dokumen, ditagihkan saat dokumen diproses dan status dikembalikan. Diskon paket Anda berlaku. Lihat [Harga](https://docs.didit.me/id/getting-started/pricing.md#harga-api-mandiri-server-to-server).

> [!NOTE]
>
> Perlu agar pengguna mengunggah dokumen sendiri, mengumpulkan hingga 3 dokumen dalam satu alur, menerjemahkan layar unggah secara otomatis, atau memverifikasi silang kolom hasil ekstraksi dengan data ID atau kuesioner? Gunakan langkah [alur kerja Document AI](https://docs.didit.me/id/core-technology/document-ai/overview.md) yang di-hosting sebagai gantinya.


## OpenAPI

````yaml id/openapi-25.json POST /v3/document-ai/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: API Verifikasi Identitas. Lakukan autentikasi dengan header x-api-key.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/document-ai/:
    post:
      tags:
        - Standalone APIs
      summary: Document AI (ekstraksi kolom kustom + pemeriksaan manipulasi)
      description: >-
        Ekstraksi kolom yang **Anda** tentukan dari dokumen pendukung apa pun —
        slip gaji, bukti dana, surat sumber kekayaan, laporan pajak, kontrak,
        faktur — dalam satu panggilan. Kirim file beserta definisi `fields`
        (kunci, nama, instruksi ekstraksi, tipe) dan dapatkan nilai bertipe
        (`text`, `number`, `date` sebagai `YYYY-MM-DD`), skor kecocokan nama
        opsional terhadap orang atau perusahaan yang diharapkan, sinyal
        manipulasi PDF/EXIF, `status` `Approved`/`Declined`/`In Review`, serta
        daftar `warnings` yang menjelaskan setiap masalah yang ditemukan. Ini
        adalah padanan server-ke-server dari langkah alur kerja Document AI yang
        di-hosting (lihat `/core-technology/document-ai/overview`); fitur ini
        menggunakan model ekstraksi default Didit dan tidak menyediakan pilihan
        penyedia.


        **Latensi.** Ekstraksi berbasis LLM: panggilan tipikal membutuhkan waktu
        **5–15 detik**, PDF multi-halaman hingga **~30 detik**. Konfigurasikan
        batas waktu klien **setidaknya 45 detik** dan jangan coba lagi sebelum
        panggilan selesai.


        **Format permintaan.** Hanya `multipart/form-data`. `fields` adalah
        **satu string array yang dienkode JSON** (1–30 item, maksimal 16 KB);
        array formulir bergaya kurung siku ditolak dengan `400`. Setiap item
        adalah `{key, name, instruction, type, required, is_full_name}` — `key`
        adalah kunci JSON yang Anda dapatkan kembali (`^[a-z0-9_]{1,64}$`,
        unik), `instruction` memberi tahu model apa yang harus dibaca, `type`
        adalah `text` (default) | `number` | `date`, `required` memunculkan
        `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` ketika nilainya tidak dapat
        dibaca, dan paling banyak satu kolom dapat mengatur `is_full_name: true`
        untuk menandai nilai yang dibandingkan dengan nama yang diharapkan.


        **Pencocokan nama.** Teruskan `expected_first_name` +
        `expected_last_name` (orang) **atau** `expected_company_name` (+
        opsional `expected_company_country`) — jangan keduanya. Nilai dari kolom
        `is_full_name` (atau, jika tidak ada yang ditandai, kolom teks pertama
        yang kuncinya menyerupai nama) dibandingkan secara fuzzy;
        `name_match_score` (0–100) dan `name_on_document` selalu dikembalikan
        jika kedua belah pihak ada, dan skor di bawah
        `document_ai_name_match_score_threshold` (default 80) menambahkan
        `DOCUMENT_AI_NAME_MISMATCH`, yang diatur oleh
        `document_ai_name_mismatch_action`.


        **Logika keputusan.** Setiap risiko dipetakan ke tindakan yang dapat
        dikonfigurasi: `DECLINE` → `status: "Declined"`, `REVIEW` → `status: "In
        Review"`, `NO_ACTION` → hanya bersifat informasi. Nilai default
        mencerminkan langkah alur kerja: dokumen tidak terbaca `REVIEW`, kolom
        wajib tidak ada `REVIEW`, dugaan manipulasi `DECLINE`, nama tidak cocok
        `NO_ACTION`, file tidak didukung `REVIEW`. Dokumen yang sama sekali
        tidak dapat diproses mengembalikan `400`; dokumen yang dapat dibaca
        tetapi bermasalah mengembalikan `200` dengan `Declined`/`In Review` —
        selalu periksa `document_ai.status` dan `document_ai.warnings`, bukan
        hanya kode HTTP.


        **Penagihan.** Setiap respons `200` menggunakan satu kredit API Document
        AI — **$0.20 per dokumen** berdasarkan tarif standar, dibayar dari
        kredit paket atau saldo isi saldo Anda. Ketika saldo organisasi tidak
        dapat menutupi panggilan, endpoint mengembalikan `403` sebelum
        pemrosesan apa pun dilakukan. Kunci API sandbox mengembalikan respons
        tiruan deterministik dan tidak dikenai biaya.


        **Persistensi.** Dengan `save_api_request=true` (default), panggilan
        disimpan sebagai sesi bertipe API: dokumen, nilai yang diekstraksi,
        definisi kolom, dan cuplikan pencocokan nama disimpan, sesi muncul di
        Business Console, dan `request_id` berfungsi dengan `GET
        /v3/session/{sessionId}/decision/`.
      operationId: post_v3document-ai
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - document
                - fields
              properties:
                document:
                  type: string
                  format: binary
                  description: >-
                    Dokumen yang akan dibaca. Ekstensi yang diizinkan: `tiff`,
                    `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Ukuran unggahan
                    maksimum: **30 MB** (berkas yang lebih besar ditolak dengan
                    `400`). PDF beberapa halaman didukung (perkirakan latensi
                    lebih tinggi); PDF terenkripsi ditolak dengan `400`. Gambar
                    dikompresi secara otomatis sebelum diproses.
                fields:
                  type: string
                  description: >-
                    Satu string array berformat JSON dengan **1–30** definisi
                    kolom (maksimum 16 KB). Setiap item: `key` (wajib,
                    `^[a-z0-9_]{1,64}$`, unik — kunci yang dikembalikan di
                    `extracted_data`), `name` (≤100 karakter, ditampilkan dalam
                    pesan), `instruction` (≤500 karakter, apa yang harus dibaca
                    model), `type` (default `text` | `number` | `date`),
                    `required` (boolean, default `false`), `is_full_name`
                    (boolean, default `false`, paling banyak satu per
                    permintaan). Array form bergaya tanda kurung, JSON tidak
                    valid, kunci duplikat, atau lebih dari satu `is_full_name`
                    akan mengembalikan `400` sebelum pemrosesan atau penagihan
                    apa pun.
                  example: >-
                    [{"key": "employer_name", "name": "Employer name",
                    "instruction": "The employer or company issuing the
                    payslip.", "type": "text", "required": true, "is_full_name":
                    false}, {"key": "employee_name", "name": "Employee name",
                    "instruction": "The full name of the employee as printed on
                    the payslip.", "type": "text", "required": true,
                    "is_full_name": true}, {"key": "net_pay", "name": "Net pay",
                    "instruction": "The net amount paid to the employee for the
                    period.", "type": "number", "required": true,
                    "is_full_name": false}, {"key": "pay_date", "name": "Pay
                    date", "instruction": "The payment date shown on the
                    payslip.", "type": "date", "required": false,
                    "is_full_name": false}]
                expected_first_name:
                  type: string
                  description: >-
                    Nama depan untuk diverifikasi terhadap kolom nama dokumen
                    (pencocokan samar, mendukung transliterasi). Gunakan bersama
                    dengan `expected_last_name`; tidak dapat digabungkan dengan
                    `expected_company_name`.
                  example: Sophia
                expected_last_name:
                  type: string
                  description: >-
                    Nama belakang untuk diverifikasi terhadap kolom nama
                    dokumen, digabungkan dengan `expected_first_name` untuk
                    `name_match_score`.
                  example: Martinez
                expected_company_name:
                  type: string
                  description: >-
                    Nama perusahaan yang akan diverifikasi terhadap kolom nama
                    pada dokumen (untuk dokumen bisnis). Tidak dapat digabungkan
                    dengan `expected_first_name` / `expected_last_name` (sengaja
                    tanpa nilai contoh — sampel permintaan yang dihasilkan
                    menggunakan kolom untuk orang; ganti kolom tersebut dengan
                    kolom ini saat memverifikasi dokumen bisnis).
                expected_company_country:
                  type: string
                  description: >-
                    Kode negara ISO 3166-1 alpha-2 opsional (dua huruf ASCII,
                    misalnya `FR`) yang menyesuaikan normalisasi nama perusahaan
                    untuk `expected_company_name`. Hanya digunakan bersama
                    `expected_company_name`.
                document_ai_name_match_score_threshold:
                  type: integer
                  minimum: 0
                  maximum: 100
                  default: 80
                  description: >-
                    `name_match_score` minimum (0–100) untuk menganggap nama
                    cocok. Skor yang lebih rendah menambahkan
                    `DOCUMENT_AI_NAME_MISMATCH`, yang diatur oleh
                    `document_ai_name_mismatch_action`.
                  example: 80
                document_ai_unreadable_document_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Tindakan yang diambil saat tidak ada kolom yang dapat dibaca
                    dari dokumen (`DOCUMENT_AI_UNREADABLE_DOCUMENT`).
                document_ai_missing_required_fields_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Tindakan yang diambil saat setidaknya satu kolom `required`
                    tidak dapat dibaca (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`;
                    `additional_data.missing_fields` mencantumkannya).
                document_ai_document_tampering_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    Tindakan yang diambil saat forensik PDF/EXIF mencurigai
                    adanya manipulasi — dimodifikasi setelah penandatanganan
                    digital, editor PDF yang dikenal, manipulasi overlay teks,
                    ekspor ulang yang mencurigakan, anomali gambar
                    (`DOCUMENT_AI_SUSPECTED_MANIPULATION`).
                document_ai_name_mismatch_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: NO_ACTION
                  description: >-
                    Tindakan yang diambil saat `name_match_score` berada di
                    bawah ambang batas (`DOCUMENT_AI_NAME_MISMATCH`).
                document_ai_unsupported_file_action:
                  type: string
                  enum:
                    - DECLINE
                    - REVIEW
                    - NO_ACTION
                  default: REVIEW
                  description: >-
                    Tindakan yang harus diambil ketika unggahan kosong atau
                    bukan file yang dapat diproses
                    (`DOCUMENT_AI_UNSUPPORTED_FILE`).
                save_api_request:
                  type: boolean
                  default: true
                  description: >-
                    Jika `true` (default), menyimpan panggilan sebagai sesi tipe
                    API — terlihat di Business Console, dapat diambil melalui
                    `GET /v3/session/{sessionId}/decision/` menggunakan
                    `request_id` yang dikembalikan, dengan dokumen yang
                    diunggah, nilai yang diekstrak, definisi kolom, dan snapshot
                    pencocokan nama yang disimpan. Jika `false`, tidak ada yang
                    disimpan dan `request_id` adalah UUID sementara hanya untuk
                    korelasi respons; panggilan tetap ditagihkan.
                  example: true
                vendor_data:
                  type: string
                  description: >-
                    String buram opsional (ID pengguna internal, email, UUID
                    Anda…) yang disimpan pada sesi yang dipertahankan dan
                    dikembalikan dalam respons. Gunakan untuk mengorelasikan
                    panggilan API dengan catatan Anda sendiri dan memfilter sesi
                    nantinya.
                  example: user-123
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Objek JSON opsional yang disimpan bersama sesi (saat
                    `save_api_request=true`) dan dikembalikan dalam respons.
                    Dalam permintaan multipart, kirimkan sebagai kolom string
                    yang dikodekan dalam JSON (misalnya
                    `metadata={"flow":"onboarding"}`) — kolom ini akan diuraikan
                    menjadi sebuah objek.
                  example:
                    flow: onboarding
      responses:
        '200':
          description: >-
            Dokumen diproses. `document_ai.status` bernilai `Approved`,
            `Declined`, atau `In Review`; setiap masalah yang terdeteksi dirinci
            dalam `document_ai.warnings`. Dokumen yang bermasalah tetap
            menghasilkan `200` — periksa isi respons, bukan hanya kode HTTP.
            Ketika `save_api_request=true`, `request_id` adalah ID sesi yang
            disimpan.
          content:
            application/json:
              examples:
                Approved:
                  summary: Slip gaji terbaca dengan jelas, nama cocok
                  value:
                    request_id: 9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c
                    document_ai:
                      status: Approved
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: 3120.5
                        pay_date: '2026-07-31'
                      detected_codes:
                        - type: CODE_128
                          page_number: 1
                          position:
                            - - 96
                              - 456
                            - - 96
                              - 616
                            - - 1012
                              - 611
                            - - 1015
                              - 456
                          x: 96
                          'y': 456
                          width: 919
                          height: 160
                          page_width: 1200
                          page_height: 1700
                          raw_payload: SU5WLTIwMjYtMDA0Mg==
                          parsed_payload: INV-2026-0042
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: 100
                      name_on_document: Sophia Martinez
                      warnings: []
                    vendor_data: user-123
                    metadata:
                      flow: onboarding
                    created_at: '2026-08-19T16:40:12.318Z'
                In Review — missing required field:
                  summary: '`net_pay` tidak dapat dibaca; tindakan default REVIEW'
                  value:
                    request_id: 3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f
                    document_ai:
                      status: In Review
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: null
                        pay_date: '2026-07-31'
                      detected_codes: []
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: 100
                      name_on_document: Sophia Martinez
                      warnings:
                        - risk: DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                          feature: DOCUMENT_AI
                          additional_data:
                            missing_fields:
                              - Net pay
                          log_type: warning
                          short_description: Required fields could not be extracted
                          long_description: >-
                            One or more fields configured as required could not
                            be extracted from the uploaded document.
                    vendor_data: null
                    metadata: null
                    created_at: '2026-08-19T16:41:05.002Z'
                Declined — suspected manipulation:
                  summary: >-
                    Forensik PDF menandai editor yang dikenal; tindakan default
                    DECLINE
                  value:
                    request_id: 7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f
                    document_ai:
                      status: Declined
                      extracted_data:
                        employer_name: Acme Payments Ltd
                        employee_name: Sophia Martinez
                        net_pay: 3120.5
                        pay_date: '2026-07-31'
                      detected_codes: []
                      fields:
                        - key: employer_name
                          name: Employer name
                          instruction: The employer or company issuing the payslip.
                          type: text
                          required: true
                          is_full_name: false
                        - key: employee_name
                          name: Employee name
                          instruction: >-
                            The full name of the employee as printed on the
                            payslip.
                          type: text
                          required: true
                          is_full_name: true
                        - key: net_pay
                          name: Net pay
                          instruction: The net amount paid to the employee for the period.
                          type: number
                          required: true
                          is_full_name: false
                        - key: pay_date
                          name: Pay date
                          instruction: The payment date shown on the payslip.
                          type: date
                          required: false
                          is_full_name: false
                      name_match_score: null
                      name_on_document: Sophia Martinez
                      warnings:
                        - risk: DOCUMENT_AI_SUSPECTED_MANIPULATION
                          feature: DOCUMENT_AI
                          additional_data:
                            detection_method: known_editor
                            processed_by_known_editor: ilovepdf
                          log_type: error
                          short_description: Suspected document manipulation
                          long_description: >-
                            The system detected signs of potential manipulation
                            or editing in the uploaded document.
                    vendor_data: null
                    metadata: null
                    created_at: '2026-08-19T16:42:40.771Z'
              schema:
                type: object
                properties:
                  request_id:
                    type: string
                    format: uuid
                    description: >-
                      ID sesi yang disimpan saat `save_api_request=true` (dapat
                      digunakan dengan `GET /v3/session/{sessionId}/decision/`);
                      jika tidak, berupa UUID korelasi sementara.
                  document_ai:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - Approved
                          - Declined
                          - In Review
                        description: >-
                          `Approved` jika tidak ada peringatan yang menghasilkan
                          penolakan atau peninjauan; `Declined` jika setidaknya
                          satu risiko dipetakan ke `DECLINE`; selain itu `In
                          Review` jika setidaknya satu dipetakan ke `REVIEW`.
                          Daftar `warnings` menjelaskan alasannya.
                      extracted_data:
                        type: object
                        additionalProperties: true
                        description: >-
                          Satu entri per kunci kolom yang diminta. Nilai diberi
                          tipe sesuai dengan `type` kolom — string untuk `text`,
                          angka untuk `number`, string `YYYY-MM-DD` untuk `date`
                          — atau `null` jika nilainya tidak dapat dibaca.
                        example:
                          employer_name: Acme Payments Ltd
                          employee_name: Sophia Martinez
                          net_pay: 3120.5
                          pay_date: '2026-07-31'
                      detected_codes:
                        type: array
                        description: >-
                          Kode QR dan barcode yang terdeteksi di setiap halaman
                          yang diproses. Kosong jika tidak ada yang ditemukan.
                          Kode QR yang ditemukan namun tidak dapat didekode
                          memiliki `parsed_payload: null`.
                        items:
                          $ref: '#/components/schemas/DocumentAIDetectedCode'
                      fields:
                        type: array
                        description: >-
                          Definisi kolom yang dinormalisasi yang digunakan untuk
                          ekstraksi ini, dikembalikan kembali (juga disimpan
                          bersama sesi jika `save_api_request=true`).
                        items:
                          type: object
                          properties:
                            key:
                              type: string
                            name:
                              type: string
                            instruction:
                              type: string
                            type:
                              type: string
                              enum:
                                - text
                                - number
                                - date
                            required:
                              type: boolean
                            is_full_name:
                              type: boolean
                      name_match_score:
                        type: number
                        nullable: true
                        minimum: 0
                        maximum: 100
                        description: >-
                          Kemiripan samar (0–100) antara `name_on_document` dan
                          nama individu/perusahaan yang diharapkan. Dikembalikan
                          setiap kali nilai nama dan nama yang diharapkan ada —
                          pada kecocokan **dan** ketidakcocokan; selain itu
                          bernilai `null`.
                        example: 96
                      name_on_document:
                        type: string
                        nullable: true
                        description: >-
                          Nilai nama yang diambil dari dokumen (kolom
                          `is_full_name`, atau kolom teks pertama yang
                          menyerupai nama) yang dibandingkan dengan nama yang
                          diharapkan. `null` jika tidak ada nilai nama yang
                          tersedia.
                        example: Sophia Martinez
                      warnings:
                        type: array
                        description: >-
                          Kosong jika persetujuan bersih. Setiap entri
                          menjelaskan satu risiko yang terdeteksi; entri dengan
                          `log_type: "error"` menetapkan `status` ke `Declined`,
                          entri dengan `log_type: "warning"` menetapkannya ke
                          `In Review`.
                        items:
                          type: object
                          properties:
                            risk:
                              type: string
                              enum:
                                - DOCUMENT_AI_UNREADABLE_DOCUMENT
                                - DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                                - DOCUMENT_AI_SUSPECTED_MANIPULATION
                                - DOCUMENT_AI_NAME_MISMATCH
                                - DOCUMENT_AI_UNSUPPORTED_FILE
                              description: >-
                                Kode risiko yang dapat dibaca mesin.
                                Masing-masing diatur oleh opsi permintaan
                                `document_ai_*_action` yang sesuai.
                              example: DOCUMENT_AI_MISSING_REQUIRED_FIELDS
                            feature:
                              type: string
                              enum:
                                - DOCUMENT_AI
                              description: >-
                                Fitur yang memunculkan peringatan. Selalu
                                `DOCUMENT_AI` pada endpoint ini.
                            additional_data:
                              type: object
                              nullable: true
                              additionalProperties: true
                              description: >-
                                Konteks tambahan untuk risiko:
                                `{"missing_fields": ["Net pay"]}` untuk kolom
                                wajib yang hilang, `{"name_on_document",
                                "score", "threshold"}` untuk ketidakcocokan
                                nama, dan temuan forensik (`detection_method`,
                                detail produsen/editor) untuk dugaan manipulasi.
                                `null` jika tidak ada yang perlu ditambahkan.
                              example:
                                missing_fields:
                                  - Net pay
                            log_type:
                              type: string
                              enum:
                                - error
                                - warning
                                - information
                              description: >-
                                Tingkat keparahan yang diturunkan dari tindakan
                                yang dikonfigurasi: `error` (DECLINE), `warning`
                                (REVIEW), `information` (NO_ACTION).
                            short_description:
                              type: string
                              description: >-
                                Ringkasan singkat satu baris yang dapat dibaca
                                manusia untuk risiko.
                              example: Required fields could not be extracted
                            long_description:
                              type: string
                              description: >-
                                Penjelasan yang mudah dibaca manusia,
                                diinterpolasi dengan `additional_data` jika
                                relevan.
                  vendor_data:
                    type: string
                    nullable: true
                    description: Salinan `vendor_data` yang Anda kirimkan, atau `null`.
                  metadata:
                    type: object
                    nullable: true
                    additionalProperties: true
                    description: Salinan objek `metadata` yang Anda kirimkan, atau `null`.
                  created_at:
                    type: string
                    format: date-time
                    description: Waktu permintaan diproses (UTC, ISO 8601).
        '400':
          description: >-
            Kesalahan validasi atau pemrosesan. Masalah tingkat kolom
            mengembalikan envelope standar DRF (satu array pesan per kolom yang
            bermasalah). Dokumen terenkripsi atau tidak terbaca serta kegagalan
            ekstraksi mengembalikan `{"error": ...}`.
          content:
            application/json:
              examples:
                Missing document:
                  summary: '`document` tidak disertakan dalam data formulir'
                  value:
                    document:
                      - No file was submitted.
                Invalid fields payload:
                  summary: '`fields` bukan satu string array yang dikodekan sebagai JSON'
                  value:
                    fields:
                      - fields must be a valid JSON array string.
                Duplicate field keys:
                  summary: Dua item memiliki `key` yang sama
                  value:
                    fields:
                      - Every fields item must have a unique key.
                Conflicting expectations:
                  summary: Ekspektasi individu dan perusahaan dikirim bersama
                  value:
                    non_field_errors:
                      - >-
                        Provide either person expected-name fields or
                        expected_company_name, not both.
                Invalid metadata:
                  summary: '`metadata` bukan objek JSON (atau berisi karakter NUL)'
                  value:
                    metadata:
                      - Metadata must be a JSON object.
                Oversized vendor_data:
                  summary: '`vendor_data` melebihi batas 2.000 byte'
                  value:
                    vendor_data:
                      - vendor_data is too long (maximum 2000 bytes).
                Encrypted PDF:
                  summary: PDF yang dilindungi kata sandi
                  value:
                    error: >-
                      The PDF is encrypted. Please upload a decrypted PDF or a
                      photo instead.
                Extraction failed:
                  summary: Pipeline ekstraksi tidak dapat memproses dokumen
                  value:
                    error:
                      - Error extracting Document AI information
              schema:
                type: object
                additionalProperties: true
        '403':
          description: >-
            Izin ditolak. Dikembalikan ketika header `x-api-key` tidak ada, dan
            juga ketika saldo organisasi pemanggil tidak dapat mencukupi biaya
            panggilan tersebut; kunci yang salah, dicabut, atau kedaluwarsa
            mengembalikan `401`. Kunci yang tidak ada mengembalikan `403` dengan
            `{"detail": ...}`; kunci yang salah, dicabut, atau kedaluwarsa
            mengembalikan `401`. Kekurangan kredit — serta organisasi dengan
            penagihan kontrak yang daftar tarifnya tidak memiliki entri API
            Document AI — mengembalikan `403` dengan `{"error": ...}` sebelum
            pemrosesan dokumen apa pun terjadi.
          content:
            application/json:
              examples:
                Missing or invalid API key:
                  summary: >-
                    Tidak ada header `x-api-key`, atau kuncinya tidak
                    valid/dicabut
                  value:
                    detail: You do not have permission to perform this action.
                Not enough credits:
                  summary: Saldo organisasi tidak mencukupi untuk panggilan ini
                  value:
                    error: >-
                      You don't have enough credits to perform this request.
                      Please top up at https://business.didit.me
                Contract rate missing:
                  summary: >-
                    Organisasi yang ditagih berdasarkan kontrak yang daftar
                    tarifnya tidak memiliki entri Document AI API
                  value:
                    error: >-
                      Document AI API is not included in your contract rate
                      card. Contact support@didit.me to enable it.
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  error:
                    type: string
        '429':
          description: >-
            Batas laju terlampaui. Endpoint POST/PATCH/DELETE berbagi anggaran
            300 permintaan penulisan per menit per kunci API (pembuatan
            transaksi dan ingesti event memiliki anggaran terpisah sebesar 600).
            Respons menyertakan header `X-RateLimit-Limit`,
            `X-RateLimit-Remaining`, `X-RateLimit-Reset`, dan `Retry-After`.
          content:
            application/json:
              examples:
                Write rate limit exceeded:
                  summary: Lebih dari 300 permintaan tulis pada menit ini
                  value:
                    detail: >-
                      Write request rate limit exceeded. You can make up to 300
                      requests per minute.
              schema:
                type: object
                properties:
                  detail:
                    type: string
        '503':
          $ref: '#/components/responses/DocumentProcessingBusy'
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # Reads DIDIT_API_KEY.
            client = Didit()

            result = client.document_ai.analyze(
                document=open("document.pdf", "rb"),
                fields="[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit, { fileFromPath } from "@didit-protocol/sdk-node";

            // Reads DIDIT_API_KEY.
            const didit = new Didit();

            const result = await didit.documentAi.analyze({
              document: await fileFromPath("document.pdf"),
              fields: "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DocumentAi.Analyze(ctx, didit.DocumentAiAnalyzeParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tFields:   \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: >-
            import java.nio.file.Path;

            import me.didit.Didit;

            import me.didit.types.DocumentAiAnalyzeParams;


            // Reads DIDIT_API_KEY.

            Didit client = new Didit();


            var result =
            client.documentAi().analyze(DocumentAiAnalyzeParams.builder()
                    .document(Path.of("document.pdf"))
                    .fields("[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]")
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;
            use Didit\File;

            // Reads DIDIT_API_KEY.
            $didit = new DiditClient();

            $result = $didit->documentAi->analyze([
                'document' => File::fromPath('document.pdf'),
                'fields' => '[{"key": "employer_name", "name": "Employer name", "instruction": "The employer or company issuing the payslip.", "type": "text", "required": true, "is_full_name": false}, {"key": "employee_name", "name": "Employee name", "instruction": "The full name of the employee as printed on the payslip.", "type": "text", "required": true, "is_full_name": true}, {"key": "net_pay", "name": "Net pay", "instruction": "The net amount paid to the employee for the period.", "type": "number", "required": true, "is_full_name": false}, {"key": "pay_date", "name": "Pay date", "instruction": "The payment date shown on the payslip.", "type": "date", "required": false, "is_full_name": false}]',
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # Reads DIDIT_API_KEY.
            didit = Didit::Client.new

            result = didit.document_ai.analyze(
              document: Pathname("document.pdf"),
              fields: "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]"
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: >-
            using System;

            using System.Text.Json;

            using Didit;


            // Reads DIDIT_API_KEY.

            using var client = new DiditClient();


            var result = await client.DocumentAi.AnalyzeAsync(new
            DocumentAiAnalyzeParams

            {
                Document = FileUpload.FromPath("document.pdf"),
                Fields = "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]",
            });

            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    DocumentAIDetectedCode:
      type: object
      required:
        - type
        - page_number
        - position
        - x
        - 'y'
        - width
        - height
        - page_width
        - page_height
        - raw_payload
        - parsed_payload
      properties:
        type:
          type: string
          description: >-
            Simbologi yang terdeteksi, misalnya `QR_CODE`, `CODE_128`, `EAN_13`,
            `DATA_MATRIX`, atau `PDF_417`.
        page_number:
          type: integer
          minimum: 1
          description: Nomor halaman berbasis 1.
        position:
          type: array
          nullable: true
          description: >-
            Poligon yang terdeteksi dalam koordinat piksel halaman yang
            dirender. Bernilai `null` jika tidak ada lokasi andal yang tersedia.
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: integer
        x:
          type: integer
          nullable: true
          description: Tepi kiri bounding box dalam piksel halaman yang dirender.
        'y':
          type: integer
          nullable: true
          description: Tepi atas bounding box dalam piksel halaman yang dirender.
        width:
          type: integer
          nullable: true
          description: Lebar bounding-box dalam piksel halaman yang dirender.
        height:
          type: integer
          nullable: true
          description: Tinggi bounding-box dalam piksel halaman yang dirender.
        page_width:
          type: integer
          description: Lebar halaman yang dirender dalam piksel.
        page_height:
          type: integer
          description: Tinggi halaman yang dirender dalam piksel.
        raw_payload:
          type: string
          description: >-
            Payload dekoder mentah sebagai string base64 jika tersedia; jika
            tidak, berupa string kosong.
        parsed_payload:
          nullable: true
          description: >-
            String atau nilai terstruktur yang didekodekan. `null` jika kode
            ditemukan tetapi tidak dapat didekodekan.
  responses:
    DocumentProcessingBusy:
      description: >-
        Terlalu banyak dokumen yang sedang diproses saat ini, sehingga unggahan
        ini tidak diproses: tidak ada yang disimpan dan tidak ada percobaan yang
        terpakai. Kirim kembali file yang sama setelah menunggu selama jumlah
        detik yang tercantum dalam `Retry-After`. Lakukan percabangan
        berdasarkan `code`, bukan pada teks pesan.
      headers:
        Retry-After:
          description: >-
            Jumlah detik yang harus ditunggu sebelum mengirimkan permintaan yang
            sama lagi.
          schema:
            type: integer
            example: 10
      content:
        application/json:
          schema:
            type: object
            required:
              - detail
              - code
            properties:
              detail:
                type: string
              code:
                type: string
                enum:
                  - document_processing_busy
          example:
            detail: We are busy processing documents. Please try again in a moment.
            code: document_processing_busy
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Kunci API aplikasi Anda, dari Developers -> API keys di Business
        Console. Kunci utama memiliki akses penuh. Kunci bernama dapat diberi
        cakupan: none, read, atau write per sumber daya, dibatasi pada beberapa
        alur kerja atau pada sesi yang disetujui, pada daftar alamat IP, dan
        pada tanggal kedaluwarsa. 401 berarti kuncinya salah, dicabut, atau
        kedaluwarsa; 403 berarti tidak ada kunci yang dikirimkan, kunci tidak
        memiliki akses ke sumber daya atau tindakan ini, atau permintaan datang
        dari alamat di luar daftar IP-nya; 404 pada rute sesi berarti sesi
        berada di luar alur kerja atau status kunci tersebut. Kunci tanpa akses
        media menerima URL gambar, video, dan PDF sebagai null, dan kunci tanpa
        izin tulis sesi menerima tautan sesi dan token sebagai null. Lihat
        https://docs.didit.me/console/api-keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.