# Dokumentasi API

Unggah dokumen dan ambil hasil bacaannya dari sistemmu sendiri. API tersedia di paket Bisnis dan Pro. Alamat dasarnya https://docbrain.ventera.ai/api/v1.

## Kunci API

Pemilik atau admin membuat kunci di Pengaturan, tab API. Teks kunci tampil sekali saat dibuat.

Kunci bisa dibatasi per izin: baca, unggah, review, buka kembali, dan ekspor. Tiga izin terakhir hanya di paket Pro. Cabut kunci kapan saja, dan kunci itu langsung ditolak.

Kirim kunci di header Authorization pada setiap permintaan. Jangan menaruhnya di kode sumber atau aplikasi yang dipakai pengguna akhir.

```
export DOCBRAIN_KUNCI="dbk_xxxxxxxx_..."
```

## Unggah dokumen: tiga langkah

Langkah 1: daftarkan dokumen. Kirim nama, jenis berkas, dan ukurannya. Jenis dokumen boleh dikosongkan agar AI mendeteksinya.

Berkas yang diterima: PDF sampai 7 MB, serta JPG, PNG, dan WEBP sampai 10 MB. Satu dokumen maksimal 20 halaman.

**PERMINTAAN**

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI" \
  -H "Content-Type: application/json" \
  -d '{"nama_file":"faktur-001.pdf","mime":"application/pdf","ukuran_byte":184233,"jenis":"invoice"}'
```

**BALASAN 201**

```
{
  "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f",
  "unggah": {
    "metode": "PUT",
    "url": "https://.../object/upload/sign/docbrain-dokumen/...?token=...",
    "kepala": { "Content-Type": "application/pdf", "x-upsert": "false" },
    "berlaku_detik": 7200
  },
  "antrekan": "/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/antrekan"
}
```

Langkah 2: kirim berkasnya ke URL unggah dengan PUT, apa adanya sebagai isi permintaan. URL berlaku dua jam dan hanya untuk satu dokumen.

```
curl -s -X PUT "<unggah.url dari balasan>" \
  -H "Content-Type: application/pdf" \
  -H "x-upsert: false" \
  --data-binary @faktur-001.pdf
```

Langkah 3: antrekan dokumen. Hanya kunci yang mendaftarkan dokumen itu yang bisa mengantrekannya.

**PERMINTAAN**

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/antrekan \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI"
```

**BALASAN 202**

```
{ "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f", "status": "antre" }
```

Mengantrekan ulang aman: balasannya sudah_diantrekan. Halaman dihitung ke kuota saat dokumen mulai dibaca, sama seperti unggahan lewat web.

## Baca dokumen dan hasilnya

Daftar dokumen organisasi, terbaru dulu. Saring dengan status, atur jumlah dengan batas (1 sampai 100).

**PERMINTAAN**

```
curl -s "https://docbrain.ventera.ai/api/v1/dokumen?status=perlu_review&batas=20" \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI"
```

**BALASAN 200**

```
{
  "data": [
    {
      "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f",
      "nama_file": "faktur-001.pdf",
      "mime": "application/pdf",
      "ukuran_byte": 184233,
      "jenis": "invoice",
      "status": "perlu_review",
      "jumlah_halaman": 1,
      "dibuat_pada": "2026-10-09T03:30:00.123456+00:00",
      "selesai_pada": "2026-10-09T03:30:07.000000+00:00",
      "galat": null
    }
  ],
  "berikutnya": "eyJ3IjoiMjAyNi0xMC0wOVQwMzozMDowMC4xMjM0NTYrMDA6MDAiLCJpIjoiMGI4ZjNlN2QtLi4uIn0"
}
```

Bila berikutnya tidak kosong, kirim nilainya sebagai kursor untuk halaman selanjutnya. Anggap kursor sebagai teks opak.

Detail satu dokumen memuat hasil aktifnya setelah dokumen selesai dibaca. Versi 0 adalah bacaan AI. Versi lebih besar adalah koreksi reviewer.

Organisasi bisa menyalakan persetujuan otomatis per jenis dokumen. Dokumen tanpa satu pun temuan langsung berstatus disetujui, dan bidang disetujui_otomatis bernilai true.

**PERMINTAAN**

```
curl -s https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI"
```

**BALASAN 200**

```
{
  "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f",
  "status": "perlu_review",
  "jenis": "invoice",
  "halaman_ditagih": 1,
  "disetujui_otomatis": false,
  "hasil": {
    "versi": 0,
    "jenis": "invoice",
    "data": { "nomor_invoice": "INV-2026-001", "total": 1110000 },
    "temuan": [ { "kode": "total_tidak_cocok", "tingkat": "janggal" } ],
    "tidak_terurai": {}
  }
}
```

Dokumen yang dihapus atau milik organisasi lain tidak pernah muncul.

## Review lewat API (paket Pro)

Kunci dengan izin Review bisa mengoreksi, menyetujui, dan menolak dokumen yang berstatus perlu_review. Izin Buka kembali dipisah, karena setara admin.

Koreksi dulu: ambil detail dokumen, ubah isian di hasil.data, lalu kirim kembali utuh bersama versi dari detail itu. Temuan dihitung ulang oleh DocBrain.

**PERMINTAAN**

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/koreksi \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI" \
  -H "Content-Type: application/json" \
  -d '{"versi":0,"data":{ ...hasil.data dari detail, dengan isian yang diperbaiki... }}'
```

**BALASAN 200**

```
{ "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f", "hasil": "disimpan", "versi": 1, "jumlah_janggal": 0 }
```

Bila versi sudah berubah, balasannya 409 versi_basi. Ambil detail lagi lalu ulangi. Satu dokumen maksimal 50 kali dikoreksi.

Setujui dengan versi terakhir. Dokumen dengan temuan janggal ditolak dengan 409 perlu_konfirmasi, kecuali kamu mengirim terima_janggal: true.

**PERMINTAAN**

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/setujui \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI" \
  -H "Content-Type: application/json" \
  -d '{"versi":1}'
```

**BALASAN 200**

```
{ "id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f", "status": "disetujui", "jumlah_janggal": 0 }
```

**BALASAN 409 UNTUK DOKUMEN JANGGAL**

```
{ "galat": { "kode": "perlu_konfirmasi", "pesan": "Dokumen punya temuan janggal. Kirim \"terima_janggal\": true untuk tetap menyetujui.", "jumlah_janggal": 1 } }
```

Tolak wajib menyertakan alasan, maksimal 500 karakter.

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/tolak \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI" \
  -H "Content-Type: application/json" \
  -d '{"versi":1,"alasan":"Berkas bukan faktur"}'
```

Buka kembali mengembalikan dokumen yang sudah disetujui atau ditolak ke perlu_review.

```
curl -s -X POST https://docbrain.ventera.ai/api/v1/dokumen/0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f/buka-kembali \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI"
```

Koreksi dan keputusan dari kunci API tercatat di jejak audit dengan sumber API dan awalan kunci. Reviewer manusia tetap bisa mengubahnya lewat web.

## Ekspor data (paket Pro)

Kunci dengan izin Ekspor mengambil dokumen yang sudah disetujui, urut waktu persetujuan. Jenis wajib. Tanggal (dari, sampai) memakai hari kalender WIB.

**PERMINTAAN**

```
curl -s "https://docbrain.ventera.ai/api/v1/ekspor?jenis=invoice&dari=2026-10-01&sampai=2026-10-09&batas=100" \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI"
```

**BALASAN 200**

```
{
  "data": [
    {
      "dokumen_id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f",
      "nama_file": "faktur-001.pdf",
      "dibuat_pada": "2026-10-09T03:30:00.123456+00:00",
      "ditinjau_pada": "2026-10-09T04:02:11.000000+00:00",
      "peninjau": "API dbk_ab12cd",
      "versi": 1,
      "janggal_saat_disetujui": 0,
      "data": { "nomor_invoice": "INV-2026-001", "total": 1110000 }
    }
  ],
  "berikutnya": null
}
```

Bila berikutnya tidak kosong, kirim nilainya sebagai kursor. Satu halaman memuat sampai 200 dokumen.

Format csv memberi satu berkas dengan kolom yang sama seperti ekspor di web, maksimal 5.000 dokumen. Persempit rentang tanggal bila lebih banyak. XLSX dan PDF hanya lewat web.

```
curl -s "https://docbrain.ventera.ai/api/v1/ekspor?jenis=invoice&format=csv" \
  -H "Authorization: Bearer $DOCBRAIN_KUNCI" -o invoice.csv
```

Setiap halaman yang dibaca tercatat di audit. Batasnya 100 halaman per jam per kunci.

## Webhook (paket Pro)

Webhook memberi tahu sistemmu saat dokumen berubah status, tanpa perlu menanyakannya berulang. Atur di Pengaturan, tab API.

Setiap webhook punya alamat dan rahasia. Rahasia tampil sekali saat dibuat atau diputar. Maksimal 3 webhook per organisasi.

- `dokumen.terbaca`: Hasil bacaan siap. Dokumen menunggu review, kecuali disetujui otomatis.
- `dokumen.gagal`: Dokumen tidak bisa dibaca. Kode galat ada di bidang galat_kode.
- `dokumen.disetujui`: Dokumen disetujui oleh reviewer, lewat API, atau otomatis (data.otomatis true).
- `dokumen.ditolak`: Dokumen ditolak.
- `dokumen.dibuka_kembali`: Dokumen yang sudah diputuskan kembali menunggu review.
- `webhook.uji`: Peristiwa percobaan dari tombol Kirim uji.

Isi pengiriman tidak memuat isi dokumen, nama berkas, atau alasan penolakan. Ambil hasilnya lewat detail dokumen.

**ISI (JSON)**

```
{
  "event_id": "5e2c7f0a-3b1d-4c8e-9a6f-1d2e3f4a5b6c",
  "tipe": "dokumen.terbaca",
  "terjadi_pada": "2026-10-09T03:30:07.412Z",
  "data": {
    "dokumen_id": "0b8f3e7d-1c2a-4b5d-9e6f-0a1b2c3d4e5f",
    "status": "perlu_review",
    "jenis": "invoice",
    "jumlah_halaman": 1,
    "versi": 0,
    "otomatis": false,
    "galat_kode": null
  }
}
```

**HEADER**

```
POST /docbrain/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: DocBrain-Webhook/1
X-DocBrain-Event: dokumen.terbaca
X-DocBrain-Event-Id: 5e2c7f0a-3b1d-4c8e-9a6f-1d2e3f4a5b6c
X-DocBrain-Timestamp: 1791516607
X-DocBrain-Signature: sha256=9f2c...e41a
```

Tanda tangan dihitung dari isi mentah: HMAC-SHA256 atas timestamp, titik, lalu isi. Tolak timestamp yang lebih dari 5 menit.

**MEMERIKSA TANDA TANGAN (NODE.JS)**

```
import { createHmac, timingSafeEqual } from 'node:crypto';

// Pakai isi MENTAH permintaan (sebelum JSON.parse). Rahasia: yang tampil saat webhook dibuat (whsec_...).
export function sahkan(isiMentah, kepala, rahasia) {
  const ts = kepala['x-docbrain-timestamp'];
  const diterima = String(kepala['x-docbrain-signature'] ?? '').replace(/^sha256=/, '');
  if (!/^\d{9,12}$/.test(ts ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // tolak yang lebih dari 5 menit
  const hitung = createHmac('sha256', rahasia).update(`${ts}.${isiMentah}`).digest('hex');
  const a = Buffer.from(diterima, 'hex');
  const b = Buffer.from(hitung, 'hex');
  return a.length === b.length && timingSafeEqual(a, b);
}

// Balas 2xx secepatnya, lalu proses. Simpan event_id untuk membuang pengiriman ganda.
app.post('/docbrain/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const isi = req.body.toString('utf8');
  if (!sahkan(isi, req.headers, process.env.DOCBRAIN_WEBHOOK_RAHASIA)) return res.sendStatus(401);
  const peristiwa = JSON.parse(isi);
  res.sendStatus(204);
  // ... ambil hasilnya: GET /api/v1/dokumen/{peristiwa.data.dokumen_id}
});
```

Alamat penerima harus https di port 443 dengan nama domain publik. Alamat IP dan jaringan lokal ditolak. Pengalihan tidak diikuti.

Balas 2xx dalam 10 detik. Balasan lain dianggap gagal dan dicoba lagi setelah 1 menit, 10 menit, 1 jam, 6 jam, lalu 24 jam.

Setelah percobaan keenam, pengiriman ditandai gagal. Dua puluh kegagalan beruntun mematikan webhook itu, dihitung sekali per 10 menit. Aktifkan lagi setelah penerima pulih.

Pengiriman bisa datang ganda dan tidak selalu berurutan. Pakai event_id untuk membuang ganda, dan ambil status terbaru dari detail dokumen.

Pengiriman digerakkan oleh aktivitas dan penjadwal harian, jadi percobaan ulang bisa tertunda. Cocokkan dengan daftar dokumen secara berkala.

Tombol Kirim uji mengirim peristiwa webhook.uji. Pengiriman yang gagal bisa dikirim ulang dari daftar pengiriman.

## Status dokumen

- `antre`: Berkas diterima dan menunggu giliran dibaca.
- `diproses`: AI sedang membaca dokumen.
- `perlu_review`: Hasil siap. Ada hal yang perlu dilihat reviewer sebelum disetujui.
- `disetujui`: Reviewer sudah menyetujui hasilnya, atau DocBrain menyetujuinya otomatis (disetujui_otomatis true).
- `ditolak`: Reviewer menolak dokumen ini.
- `gagal`: Dokumen tidak bisa dibaca. Lihat kode dan pesan di bidang galat.

Tanyakan status secara berkala, misalnya tiap 5 sampai 10 detik. Satu dokumen biasanya selesai dalam hitungan detik.

## Galat dan batas

Galat selalu berbentuk JSON dengan kode yang bisa diproses program.

```
{ "galat": { "kode": "kuota_habis", "pesan": "Kuota halaman bulan ini sudah habis." } }
```

| STATUS | KODE | ARTI |
| --- | --- | --- |
| 401 | kunci_tidak_sah | Kunci salah, dicabut, atau organisasi tidak berlangganan paket dengan API. |
| 403 | izin_ditolak | Kunci tidak punya izin untuk tindakan itu. |
| 403 | kuota_habis | Kuota halaman bulan ini habis. |
| 403 | langganan_tidak_aktif | Langganan organisasi tidak aktif. |
| 400 | masukan_tidak_sah | Isian permintaan salah. Pesannya menyebut isian yang bermasalah. |
| 404 | tidak_ditemukan | Dokumen tidak ada, milik organisasi lain, atau sudah dihapus. |
| 409 | berkas_belum_ada | Berkas belum diunggah ke URL unggah. |
| 409 | versi_basi | Versi data sudah berubah. Ambil detail dokumen lagi. |
| 409 | bukan_perlu_review | Dokumen tidak sedang menunggu review. |
| 409 | perlu_konfirmasi | Dokumen punya temuan janggal. Kirim terima_janggal: true untuk menyetujui. |
| 409 | bukan_keputusan | Dokumen belum disetujui atau ditolak, jadi tidak bisa dibuka kembali. |
| 409 | batas_versi | Dokumen sudah 50 kali dikoreksi. |
| 422 | tanpa_hasil | Dokumen belum punya hasil bacaan. |
| 429 | batas_ekspor | Batas 100 halaman ekspor per jam tercapai. |
| 413 | terlalu_besar | Isi permintaan terlalu besar. Kirim hanya metadata. |
| 422 | mime_tidak_cocok | Jenis berkas yang terunggah berbeda dari yang didaftarkan. |
| 422 | berkas_kosong | Berkas yang terunggah kosong. |
| 422 | berkas_terlalu_besar | Berkas melewati batas ukuran. |
| 429 | terlalu_banyak_permintaan | Lebih dari 120 permintaan dalam semenit. Tunggu sesuai Retry-After. |
| 429 | batas_harian | Batas 200 unggahan per hari untuk kunci ini tercapai. |
| 429 | batas_harian_organisasi | Batas 1.000 unggahan per hari untuk organisasi tercapai. |
| 429 | terlalu_banyak_menunggu | Terlalu banyak dokumen yang belum selesai diunggah. |
| 500 | galat_server | Galat di sisi DocBrain. Coba lagi nanti. |

- 120 permintaan per menit per kunci.
- 200 unggahan per hari per kunci, dan 1.000 per hari per organisasi.
- Maksimal 50 dokumen yang belum selesai diunggah per kunci.
- Unggahan memakai kuota halaman organisasi, sama seperti lewat web.

## Contoh lengkap (JavaScript)

```
const kunci = process.env.DOCBRAIN_KUNCI;
const kepala = { Authorization: `Bearer ${kunci}`, 'Content-Type': 'application/json' };

// 1. Daftarkan dokumen dan dapatkan URL unggah.
const dibuat = await fetch('https://docbrain.ventera.ai/api/v1/dokumen', {
  method: 'POST',
  headers: kepala,
  body: JSON.stringify({ nama_file: berkas.name, mime: berkas.type, ukuran_byte: berkas.size }),
}).then((r) => r.json());

// 2. Unggah berkasnya apa adanya ke URL itu.
await fetch(dibuat.unggah.url, { method: 'PUT', headers: dibuat.unggah.kepala, body: berkas });

// 3. Antrekan, lalu tanya hasilnya sampai selesai.
await fetch('https://docbrain.ventera.ai' + dibuat.antrekan, { method: 'POST', headers: kepala });
const hasil = await fetch('https://docbrain.ventera.ai/api/v1/dokumen/' + dibuat.id, { headers: kepala }).then((r) => r.json());
```

## Keamanan

- DocBrain menyimpan hash kunci, bukan kuncinya. Kunci yang hilang tidak bisa dipulihkan: buat yang baru.
- Setiap kunci hanya melihat dokumen organisasinya sendiri. Kunci tetap berlaku sampai dicabut, juga bila pembuatnya keluar dari organisasi.
- Unggahan, bacaan hasil, dan pencabutan kunci tercatat di jejak audit organisasi, tanpa isi dokumen.
- Izin review, buka kembali, dan ekspor hanya untuk paket Pro. Bila paket turun, kunci itu berhenti bekerja tanpa dicabut.
- Webhook ditandatangani dengan rahasia per webhook. Putar rahasia bila bocor; yang lama langsung tidak berlaku.

Butuh bantuan? Hubungi tim Ventera melalui ventera.ai.
