---
name: apipedia
description: Memanggil gateway APIPedia (data Indonesia, OCR, vision, QR/barcode buat dan baca, urai URL, BMKG, kurs, ongkir, Gmail/Google/LinkedIn/GitHub lewat akun terhubung, dan lainnya) dengan satu Bearer token. Pakai skill ini setiap kali tugas menyebut APIPedia atau butuh salah satu endpoint di bawah.
---

# APIPedia Gateway

## Ringkasan

APIPedia adalah satu API gateway untuk banyak layanan: data wilayah, kode pos,
hari libur, jadwal sholat, parsing NIK/NPWP, validasi rekening, OCR KTP/SIM,
vision (hapus latar, cocokkan wajah, liveness), buat dan baca QR code/barcode,
urai URL, QRIS parser, gempa dan
cuaca BMKG, kurs ECB, ongkir, data kampus/rumah sakit/sekolah, geocoding
OpenStreetMap, downloader media, serta aksi Gmail, Google Calendar/Docs/Sheets/
Drive/Meet, LinkedIn, dan GitHub lewat akun yang sudah dihubungkan pengguna.

Skill ini memuat 210 operasi (208 aktif, 2 segera hadir). Bagian
"Referensi endpoint" di bawah dibangkitkan langsung dari spesifikasi OpenAPI
console, jadi selalu sama dengan referensi di https://apipedia.id/docs.

Dokumentasi dan sandbox ada di situs publik:

- https://apipedia.id/docs - panduan dan referensi endpoint per API (`/docs/<slug>`)
- https://apipedia.id/docs/mcp - pasang server MCP APIPedia (Claude, Cursor)
- https://apipedia.id/docs/skill - unduh SKILL.md ini
- https://apipedia.id/sandbox - coba endpoint dari browser

Halaman console yang relevan:

- `/api/token` - buat API key
- `/connections` - hubungkan akun Gmail, Google, LinkedIn, GitHub
- `/billing` - paket, kuota, saldo, voucher
- `/tickets` - tiket dukungan

## Base URL

Gateway dilayani DUA proses. Token dan amplop responsnya sama; yang berbeda
hanya alamatnya. Memakai base yang salah menghasilkan 404, bukan pesan error
yang menjelaskan. Setiap endpoint di referensi bawah sudah mencantumkan URL
lengkapnya, jadi salin dari sana.

| Base URL | Melayani |
|---|---|
| `https://api.apipedia.id/v1` | Express: semua endpoint komputasi dan data (wilayah, OCR, vision, QR, barcode, URL, BMKG, kurs, ongkir, maps, dst.) |
| `https://api.apipedia.id/v1` | Edge Function: akun, kuota, penggunaan, log, paket, tagihan |
| `https://api.apipedia.id/v1` | Edge Function: kelola API key |
| `https://api.apipedia.id/v1` | Edge Function: login dan sesi console |
| `https://api.apipedia.id/v1` | Edge Function: katalog produk |
| `https://api.apipedia.id/v1` | Edge Function: koneksi akun dan aksi /toolkits/* |
| `https://api.apipedia.id/v1` | Edge Function: saldo dan isi saldo |
| `https://api.apipedia.id/v1` | Edge Function: voucher |
| `https://api.apipedia.id/v1` | Edge Function: tiket dukungan |
| `https://api.apipedia.id/v1` | Edge Function: Email Tracking (piksel /track/open/* publik) |
| `https://api.apipedia.id/v1` | Edge Function: CMS blog (khusus admin) |

Aturan alamat Edge Function: `<base>/functions/v1/<nama function>/v1/<path>`.
`/v1` memang muncul dua kali, dan nama function tidak selalu sama dengan
segmen pertama path (`/me`, `/usage`, `/logs`, `/plans`, `/billing` dilayani
function `account`; `/connections` dan `/toolkits` dilayani `integrations`).
`https://api.apipedia.id/v1/<path>` adalah pintu tunggal gateway: satu alamat untuk semua endpoint,
dan pemetaan ke Edge Function atau Express dikerjakan di sisi server.

## Autentikasi

Semua endpoint kecuali yang ditandai "tanpa token" wajib mengirim:

```
Authorization: Bearer <token>
```

Ada dua jenis token yang diterima di kedua base URL:

| Jenis | Bentuk | Dari mana | Kapan dipakai |
|---|---|---|---|
| API key | `apk_` + 48 karakter hex | Halaman Token (`/api/token`) atau `POST /tokens` | Integrasi server, skrip, dan agent. Pilihan yang benar untuk skill ini. |
| JWT sesi | JWT ES256, berlaku 900 detik | `POST /auth/login` dari console | Hanya untuk console di browser. Login butuh Turnstile, jadi tidak cocok untuk agent. |

Detail API key:

- Nilai penuh hanya ditampilkan SEKALI saat dibuat atau dirotasi. Simpan sebagai
  environment variable `APIPEDIA_TOKEN`, jangan pernah ditulis di kode yang di-commit.
- `scopes` berisi daftar slug produk katalog (lihat `GET /apis`). Kosong berarti
  boleh memanggil seluruh gateway. Kalau diisi, memanggil endpoint di luar
  cakupan dijawab 403.
- Bisa punya masa berlaku (`expires_in_days` 1-3650). Kunci kedaluwarsa dijawab
  401 dengan pesan khusus, berbeda dari kunci tak dikenal.
- Maksimal 20 kunci aktif per akun.
- Pencabutan, rotasi, dan perubahan cakupan berlaku penuh paling lambat 30
  detik (hasil resolusi kunci di-cache 30 detik).
- Cara cepat menguji token: `GET /auth/me` mengembalikan pemilik token dan
  `auth_method` (`api_key` atau `jwt`).

```bash
export APIPEDIA_TOKEN="apk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
curl "https://api.apipedia.id/v1/auth/me" -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

## Format respons

Setiap endpoint (sukses maupun gagal) mengembalikan amplop yang sama, dengan
urutan kunci yang tetap:

```json
{
  "is_success": true,
  "message": "Teks penjelasan berbahasa Indonesia",
  "data": {}
}
```

- Periksa `is_success` sebelum membaca `data`.
- Saat gagal, `is_success` bernilai false, `data` biasanya null, dan `message`
  menjelaskan penyebabnya dalam bahasa Indonesia. Tampilkan `message` apa adanya
  ke pengguna; isinya ditulis untuk dibaca manusia.
- Beberapa error 503 mengisi `data` dengan detail, mis. `{ "missingEnv": ["COMPOSIO_API_KEY"] }`.
- Satu-satunya pengecualian: `GET /connections/{toolkit}/callback` menjawab
  redirect 302, bukan JSON.

## Kode status dan error

| Status | Arti | Contoh `message` nyata | Tindakan agent |
|---|---|---|---|
| 200 / 201 | Berhasil (201 untuk pembuatan: daftar, token, topup, tiket) | `QR code berhasil dibuat` | Baca `data` |
| 400 | Parameter atau body tidak valid; juga dipakai beberapa "tidak ditemukan" di data wilayah/libur/ongkir | `Field "text" wajib diisi.`, `Parameter "mulai" wajib diisi dengan tanggal format YYYY-MM-DD.` | Perbaiki input sesuai `message`. Jangan retry mentah. |
| 401 | Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend | `Unauthorized: missing bearer token`, `Unauthorized: invalid or expired token`, `Unauthorized: API key sudah melewati masa berlakunya` | Minta pengguna membuat/memperpanjang API key di `/api/token`. Jangan retry. |
| 403 | API key valid tetapi cakupannya tidak mencakup endpoint ini; atau endpoint khusus admin | `Forbidden: API key ini tidak diberi akses ke endpoint tersebut. Ubah cakupannya di halaman Token.` | Minta pengguna menambah slug produk ke `scopes` kunci. |
| 404 | Path tidak dikenal (sering karena base URL salah) atau sumber daya tidak ada | `Endpoint GET /v1/contoh tidak ditemukan.`, `Tiket tidak ditemukan.` | Cek base URL dan path. |
| 409 | Akun pihak ketiga belum terhubung (`/toolkits/*`) | `Anda belum menghubungkan akun Gmail. Buka halaman Koneksi di console APIPedia, ...` | Arahkan pengguna ke `/connections`, tunggu status `active`, baru ulangi. |
| 413 | Body melebihi batas ukuran | `Ukuran permintaan melebihi batas maksimum N MB.` | Perkecil gambar/lampiran. |
| 422 | Input sah tapi tidak bisa diproses (wajah tidak terdeteksi, media privat) | `Wajah tidak terdeteksi pada gambar yang dikirim.` | Minta input lain. |
| 429 | Disiapkan untuk rate limit/kuota per paket | `Kuota harian habis. Coba lagi setelah 00:00 WIB.` | Tunggu sampai reset (lihat `/me/quota`), retry dengan backoff. |
| 500 | Kesalahan internal (detail tidak dibocorkan) | `Terjadi kesalahan internal pada server.` | Retry sekali dengan jeda; kalau berulang, buat tiket. |
| 502 | Sumber hulu gagal (BMKG, ECB, OpenStreetMap, ip-api, Composio, Tripay, mesin OCR) | `Sumber data gempa (BMKG) tidak bisa dihubungi saat ini.` | Retry dengan backoff eksponensial (mis. 2s, 4s, 8s). |
| 503 | Fitur belum dikonfigurasi di server | `Aksi Gmail belum bisa dipakai: COMPOSIO_API_KEY masih kosong di server. ...` | Jangan retry; laporkan ke pengguna. |
| 504 | Pemrosesan melewati batas waktu (vision 180 detik, downloader 45 detik) | `Pencocokan wajah melewati batas waktu pemrosesan.` | Retry sekali, atau perkecil input. |

## Kuota dan rate limit

Semua paket membuka seluruh API; yang berbeda hanya batasnya:

| Paket | Request per hari | Request per detik | Riwayat log terbaca |
|---|---|---|---|
| Free (bawaan) | 500 | 10 | 7 hari |
| Pro (lewat voucher) | 25.000 | 60 | 90 hari |
| Business (kontrak) | 250.000 | 300 | 90 hari |

- Kuota harian dihitung per akun (semua API key milik akun berbagi kuota yang
  sama) dan reset tengah malam WIB (17:00 UTC).
- `GET /me/quota` mengembalikan `used`, `limit`, `resetAt`, `rateLimit`, dan
  `tier`. Panggil sebelum batch besar.
- Panggilan ke endpoint platform (auth, token, akun) tidak dihitung sebagai
  pemakaian API produk di laporan `/usage`.
- Hormati `rateLimit` paket di sisi klien walaupun server belum menolak: jangan
  memparalelkan lebih dari batas per detik, dan khusus `/maps/*` (Nominatim)
  jangan lebih dari satu permintaan per detik.
- Kenaikan paket ke Pro didapat lewat voucher (`POST /vouchers/redeem`), bukan
  pembelian.

## Endpoint yang butuh koneksi akun

`/toolkits/*` (Gmail, Google Calendar/Docs/Sheets/Drive/Meet, LinkedIn,
GitHub) bertindak atas nama akun pihak ketiga milik pengguna, lewat Composio.

1. Cek status: `GET /connections/{toolkit}/status`.
2. Kalau belum `active`: `POST /connections/{toolkit}/initiate`, berikan
   `redirectUrl` ke pengguna untuk dibuka di browser, lalu polling status
   sampai `active` (atau minta pengguna memakai halaman `/connections`).
3. Baru panggil `/toolkits/...`. Tanpa koneksi aktif jawabannya 409.

Akun yang tertahan di `pending` karena halaman izin sempat ditutup bisa
dilanjutkan lewat `POST /connections/{id}/resume`: jawabannya `redirectUrl`
baru (atau yang lama, bila masih segar) untuk akun yang sama.

Docs to PDF memakai koneksi Google Docs yang sama:
`GET /toolkits/googledocs/documents/{documentId}/pdf` mengembalikan
`downloadUrl` berumur pendek (bawaan), PDF mentah (`?output=binary`), atau
base64 (`?output=base64`). Batas Google 10 MB per dokumen.

`/mail/*` (Email IMAP/SMTP) dan `/drive/*` (Cloud Drive: Dropbox, OneDrive,
Yandex Disk, MEGA, pCloud, S3/R2, Telegram) memakai kredensial yang disimpan
terenkripsi, bukan Composio. Cek akun dengan `GET /mail/accounts` atau
`GET /drive/accounts`; bila kosong (jawaban 412), minta pengguna
menghubungkannya di halaman `/connections` console (atau `POST /mail/accounts`,
`POST /drive/accounts`, `POST /drive/oauth/{provider}/start`). Pilih akun dengan
`connection_id`; tanpa itu dipakai akun default (409 bila ada beberapa akun
tanpa default). `POST /mail/send` mengirim email sungguhan - konfirmasi dulu.

Semua `POST /toolkits/*` punya efek samping nyata (email terkirim, event dibuat,
postingan tayang, issue dibuka). Konfirmasi ke pengguna sebelum memanggilnya.
`data.result` berisi jawaban penyedia apa adanya, jadi bentuk persisnya
mengikuti API Gmail/Google/LinkedIn/GitHub.

## HTML to PDF

`POST /pdf/html` (Express, produk `html-to-pdf`) mengubah HTML mentah atau URL
publik menjadi PDF. Isi TEPAT SATU dari `html` (maks 2 MB) atau `url`.
Jawaban bawaan adalah BERKAS `application/pdf`, bukan JSON - simpan body-nya
apa adanya (`curl -o hasil.pdf`). Untuk amplop JSON tambahkan
`?output=base64`.

Mesinnya BUKAN browser: flexbox, CSS Grid, dan stylesheet eksternal tidak
didukung, dan JavaScript tidak pernah dijalankan. Sebelum menyarankan endpoint
ini ke pengguna, periksa dulu bentuk HTML-nya - kalau tata letaknya bergantung
flexbox/grid, katakan hasilnya akan bergeser, jangan dijanjikan sama persis
dengan tampilan di browser. Halaman yang isinya digambar JavaScript akan
menghasilkan PDF kosong. Cocok untuk invoice, kuitansi, laporan, dan tabel.

Gambar hanya diambil dari https publik (JPEG/PNG, maks 5 MB, 50 gambar) dan
yang gagal dibuang diam-diam; alamat privat/internal ditolak; batas 20 detik
(504) dan 100 halaman.

## Email Tracking

Dua langkah: `POST /track/emails` (dengan token) menghasilkan `id`,
`pixelUrl`, dan `htmlSnippet`; sematkan snippet itu di HTML email. Piksel
`GET /track/open/{id}.gif` dimuat klien email TANPA token - jangan memanggilnya
sendiri, karena itu tercatat sebagai buka. Baca hasil di
`GET /track/emails/{id}` (`status`, `opened`, `openCount`, `events`).

Opsi saat membuat: `startAfterMinutes` atau `startAt` (ISO dengan zona
waktu) supaya buka oleh pengirim sendiri tidak terhitung, `expiresAt`,
`countProxyAsOpen: false`, dan `webhookUrl` + `webhookEvents`
(`first`/`every`). `webhookSecret` hanya dikembalikan sekali; verifikasi
`X-APIPedia-Signature: sha256=<hex HMAC-SHA256 body mentah>`. Ubah lewat
`PATCH /track/emails/{id}`; buka oleh pemilik sendiri dikecualikan lewat
`POST /track/emails/{id}/events/{eventId}/exclude` (batalkan: `/include`).

Saat melaporkan hasil ke pengguna: "belum dibuka" bukan berarti "belum dibaca"
(klien bisa memblokir gambar), dan event `likelyProxy: true` (Gmail
GoogleImageProxy, Apple Mail Privacy Protection) bisa terjadi otomatis tanpa
email dibuka manusia. Pakai `humanOpenSeen` untuk bukti terkuat.

## Blog

Artikel di https://apipedia.id/blog dibaca lewat Express, TANPA token:

- `GET /blog/posts` - daftar artikel terbit, berhalaman (`page`, `pageSize`
  maks 50, `tag`). Artikel terjadwal baru muncul setelah waktunya lewat.
- `GET /blog/posts/{slug}` - isi lengkap satu artikel; `body_html` sudah
  disanitasi. Draf dijawab 404.

Menulis artikel hanya bisa dilakukan ADMIN, lewat Edge Function `blog`
(`/admin/blog*`). Token milik akun non-admin - termasuk API key - selalu
dijawab 403; jangan mencoba ulang. Alur penulisan: `POST /admin/blog`
(membuat draf; slug dibuat dari judul bila kosong, slug bentrok = 409), lalu
`PATCH /admin/blog/{id}` untuk mengubah, dan
`POST /admin/blog/{id}/publish` dengan `{ "publish": true }` untuk
menerbitkan (`published_at` di masa depan = terjadwal) atau
`{ "publish": false }` untuk menurunkan ke draf. Konfirmasi ke pengguna
sebelum menerbitkan atau menghapus: artikel terbit langsung terlihat publik.

## QR code, barcode, dan URL

Semua dihitung di server Express tanpa panggilan jaringan keluar, butuh token
seperti endpoint produk lain, dan di-cache 24 jam (header `X-Cache: HIT|MISS`).
Cakupan API key: slug `qr-code-generator` membuka `/qr/*` (buat dan baca QR),
`barcode-generator` membuka `/barcode/*` (buat dan baca barcode), `url-parser`
membuka `/url/*`.

### Mengurai URL

`GET /url/parse?url=...` atau `POST /url/parse` dengan body `{ "url": "..." }`.
URL tidak pernah dihubungi. Hasilnya memuat `scheme`, `hostname`,
`hostname_unicode`, `port`/`effective_port`, `pathname`, `path_segments`,
`query` (objek; kunci berulang menjadi array), `fragment`, `tld` (public
suffix, mis. `co.id`), `domain`, `subdomain`, `is_ip`, dan `ip_version`.
Masukan tanpa skema diberi https:// dan ditandai `assumed_scheme: true`.
Batas 8 KB.

```bash
curl "https://api.apipedia.id/v1/url/parse?url=https%3A%2F%2Fblog.apipedia.co.id%2Fartikel%3Ftag%3Dnik" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Untuk URL yang membawa kredensial, token, atau yang sangat panjang, SELALU
pakai POST: GET dengan kredensial tidak di-cache tetapi tetap tercatat di log
akses sebagai query string. Kata sandi userinfo tidak pernah dikembalikan
(`has_password: true`, `***` di `input`/`normalized`).

```bash
curl -X POST "https://api.apipedia.id/v1/url/parse" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://USER:PASSWORD@files.apipedia.id/unduh?token=abc123"}'
```

### Membuat barcode

`POST /barcode/generate`. `symbology` (default `code128`): 1D `code128`,
`code39`, `ean13`, `ean8`, `upca`, `upce`, `itf14`, `codabar`; 2D `qrcode`,
`datamatrix`, `pdf417`, `azteccode`. `format` `png` (default) atau `svg`.
`scale` 1-20 (default 3); `width`/`height` 32-4000 piksel adalah batas atas,
ukuran aktual ada di respons. `include_text` (default true, hanya 1D).
`error_correction` L/M/Q/H (hanya qrcode). `data` maksimal 4.096 byte.

- Digit periksa EAN-13/EAN-8/UPC-A/ITF-14: kirim tanpa digit terakhir (12 digit
  untuk EAN-13) supaya dihitung otomatis. Digit periksa yang salah dijawab 400
  dan `message` menyebut digit yang benar. `data.data` di respons berisi data
  akhir termasuk digit periksa.
- Simbologi 1D hanya ASCII. Untuk teks Unicode pakai simbologi 2D: teks
  non-ASCII dikodekan UTF-8 dengan ECI 26 (`data.eci: "utf-8"`).
- Gambar ada di `data.image_data_url` dan `data.image_base64`.

```bash
curl -X POST "https://api.apipedia.id/v1/barcode/generate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"symbology":"ean13","data":"899123456789","format":"svg"}'
```

Untuk QR bergaya vCard/WiFi siap pakai, `POST /qr/generate` tetap tersedia;
`/barcode/generate` dengan `symbology: "qrcode"` memberi kontrol skala, SVG,
dan ECI.

### Membaca QR code dan barcode dari gambar

`POST /qr/decode` (hanya QR, Micro QR, rMQR) dan `POST /barcode/decode` (semua
format; `formats` opsional, mis. `["EAN13","Code128"]`, juga `AllLinear` dan
`AllMatrix`). Body: `image_base64` wajib (base64 mentah atau data URL; PNG,
JPEG, WebP, GIF, BMP; maks 8 MB dan 40 megapiksel), `try_harder` (default
true), `max_symbols` 1-50 (default 10), `charset` opsional.

```bash
IMG=$(base64 -w0 struk.png)
curl -X POST "https://api.apipedia.id/v1/barcode/decode" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"image_base64\":\"$IMG\",\"formats\":[\"QRCode\",\"EAN13\"]}"
```

Respons: `count`, `symbols[]`, `formats_searched`, `image { mime, bytes }`.
Setiap simbol: `format`, `format_label`, `text`, `encoding`,
`encoding_source`, `has_eci`, `eci`, `text_is_binary`, `bytes_length`,
`bytes_base64`, `bytes_hex`, `text_hri` (GS1), `position`, `orientation`,
`is_mirrored`, `is_inverted`, `symbology_identifier`, dan `parsed`.

Aturan encoding (penting untuk teks non-Latin dan data biner):

- `bytes_hex`/`bytes_base64` adalah byte mentah persis seperti di simbol.
  Itulah sumber kebenaran; `text` hanyalah tafsiran byte tersebut. Simpan atau
  teruskan byte mentah bila isinya akan diproses mesin.
- `encoding_source: "eci"` berarti simbol menyatakan charset-nya sendiri (ECI);
  hasilnya bisa dipercaya. `"guessed"` berarti pembaca menebak - periksa
  `text` dan, kalau terlihat rusak (mojibake), ulangi dengan `charset` yang
  benar (mis. `shift_jis`, `gb18030`, `big5`, `euc-kr`, `windows-1252`).
  `"request"` berarti `charset` dari body dipakai dan menang atas ECI.
- `text_is_binary: true` (atau `parsed.type: "binary"`) berarti isinya bukan
  teks: abaikan `text`, pakai `bytes_base64`.
- `parsed.type`: url (berisi hasil /url/parse), wifi, vcard, mecard, email,
  tel, sms, geo, qris (diurai seperti /qris/parse), gs1, binary, atau text.

Gambar tanpa barcode BUKAN error: jawabannya HTTP 200, `is_success: true`,
`count: 0`, `symbols: []`, dan `hint` berisi saran. Periksa `count` sebelum
membaca `symbols[0]`; minta gambar yang lebih tajam/ter-crop alih-alih
me-retry permintaan yang sama (hasilnya di-cache). Status 400 hanya untuk
masukan rusak: base64 tidak valid, bukan gambar yang didukung, melebihi
batas, nama format tidak dikenal, atau charset tidak dikenal.

## Server MCP

APIPedia juga menyediakan server Model Context Protocol di:

```
https://api.apipedia.id/v1/mcp
```

Agent yang mendukung MCP bisa memakai alamat itu sebagai alternatif dari
memanggil REST secara manual: alat (tools) MCP-nya memetakan endpoint gateway
yang sama. Ada dua cara masuk:

- **Login OAuth (disarankan untuk Claude, ChatGPT, Cursor, VS Code).** Tambahkan
  alamat di atas sebagai connector/server MCP tanpa header. Klien menerima
  `401` dengan `WWW-Authenticate: resource_metadata=...`, menemukan
  authorization server di `/.well-known/oauth-authorization-server`, mendaftar
  sendiri (DCR), lalu membuka browser ke halaman login MCP APIPedia. Pengguna
  masuk, memilih API key yang dipakai (atau dibuatkan otomatis), lalu menekan
  Izinkan. Akses dicabut dengan mencabut API key itu di halaman Token API.
- **API key langsung.** Untuk klien yang hanya mendukung header statis, kirim
  `Authorization: Bearer $APIPEDIA_TOKEN` (token yang sama dengan REST).

Kalau server MCP tidak menjawab, gunakan endpoint REST di skill ini. Aturan
kuota, cakupan API key, dan amplop respons tetap sama di ketiga jalur.

## Hubungkan agent dengan kode (tanpa API key di awal)

Kalau pengguna meminta "hubungkan platformku ke apipedia.id" dan belum
memberikan API key, jangan minta pengguna menyalin kunci. Pakai alur kode
perangkat (RFC 8628). Petunjuk mesin-terbacanya: `GET https://api.apipedia.id/v1/device`.

1. Minta kode (tanpa token):

```bash
curl -X POST https://api.apipedia.id/v1/device/code \
  -H "Content-Type: application/json" \
  -d '{"client_name":"Claude Code","agent_name":"Agent Toko Budi"}'
```

Jawabannya: `device_code` (rahasia, jangan ditampilkan), `user_code` (mis.
`2WKM-NH3C`), `verification_uri`, `verification_uri_complete`, `expires_in`
(600 detik), dan `interval` (5 detik).

2. Tampilkan ke pengguna, persis:

> Buka link ini: https://console.apipedia.id/device?code=2WKM-NH3C — login, cek nama agent-nya, centang kotaknya, lalu tekan Setujui.

Pengguna juga bisa mengetik kodenya di console: Settings > Keamanan >
"Hubungkan agent dengan kode".

3. Poll tiap `interval` detik:

```bash
curl -X POST https://api.apipedia.id/v1/device/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"urn:ietf:params:oauth:grant-type:device_code","device_code":"<device_code>"}'
```

| Jawaban (`error`, HTTP 400) | Arti |
|---|---|
| `authorization_pending` | Pengguna belum menyetujui. Tunggu `interval` detik lalu poll lagi. |
| `slow_down` | Terlalu sering. Tambah jeda 5 detik (nilai baru ada di `interval`). |
| `access_denied` | Pengguna menolak. Berhenti. |
| `expired_token` | Kode habis (10 menit). Minta kode baru. |
| `invalid_grant` | `device_code` tidak dikenal atau kuncinya sudah diserahkan. |

Sukses (HTTP 200) mengembalikan `access_token` berupa **API key `apk_` biasa**
dengan akses penuh ke seluruh gateway, `token_type: "Bearer"`, plus `mcp_url`.
Kunci hanya diserahkan SEKALI. Simpan sebagai `APIPEDIA_TOKEN`, lalu pakai
`Authorization: Bearer $APIPEDIA_TOKEN` untuk REST maupun server MCP. Pengguna
mencabutnya lewat kunci "Agent: <nama>" di halaman Token API.

## Aturan untuk agent

1. Selalu kirim `Authorization: Bearer $APIPEDIA_TOKEN` (kecuali endpoint tanpa
   token). Jangan pernah mencetak, mencatat, atau meng-commit nilai token.
2. Salin URL lengkap dari referensi; jangan menebak base URL.
3. Baca `is_success` sebelum memakai `data`; tampilkan `message` saat gagal.
4. Retry hanya untuk 5xx dan 429, dengan backoff. Jangan retry 400/401/403/404/409.
5. Jangan memanggil endpoint bertanda BELUM AKTIF atau INTERNAL.
6. Gambar untuk `/ocr/*` dan `/vision/*` dikirim sebagai base64 (data URL juga
   diterima) di field JSON, maksimal 8 MB setelah didekode, format PNG/JPEG/WebP.
7. `/nik/parse`, `/npwp/parse`, dan `/rekening/validate` memeriksa STRUKTUR,
   bukan keberadaan di Dukcapil, DJP, atau bank. Jangan menyimpulkan identitas
   seseorang sah dari hasilnya.
8. `/ocr/*` memakai Tesseract dan bisa salah baca. Selalu tunjukkan `raw_text`
   kalau field yang dibutuhkan bernilai null; jangan dipakai sebagai verifikasi identitas.
9. `/vision/face/liveness` adalah penilaian pasif dari satu gambar, bukan
   pembuktian identitas. Jangan jadikan satu-satunya dasar keputusan berisiko tinggi.
10. `/ongkir/cek` adalah estimasi dari data tarif statis, bukan tarif resmi kurir.
11. `/kurs/*` memakai kurs referensi ECB, bukan JISDOR Bank Indonesia dan bukan
    kurs jual-beli bank.
12. `/gempa/*` dan `/cuaca` meneruskan data BMKG: sebutkan "Data: BMKG". Untuk
    peringatan dini gempa/tsunami arahkan pengguna ke kanal resmi BMKG.
13. `/cuaca` hanya menerima kode adm4 (kelurahan/desa) seperti `31.71.03.1001`.
14. `/maps/*` memakai OpenStreetMap Nominatim: cantumkan "© OpenStreetMap
    contributors (ODbL)" saat menampilkan hasil.
15. `/kodepos/*` memetakan kode pos ke KECAMATAN, bukan kelurahan atau jalan.
16. `/sekolah/cari` selalu berhalaman: baca `total` dan `total_pages`.
17. `/qris/parse` adalah analisis struktur offline, bukan verifikasi merchant.
    `crc_valid: true` hanya berarti payload utuh.
18. `/dl/*` mengembalikan tautan CDN yang kedaluwarsa dalam hitungan jam
    (`formats_expire: true`); unduh segera dan hanya untuk konten yang boleh diunduh.
19. Untuk melaporkan bug atau data salah, buat tiket lewat `POST /tickets`
    (`/report` belum aktif).
20. `/admin/blog*` hanya untuk akun admin. Jawaban 403 di sana berarti token
    bukan milik admin, bukan galat sementara.
21. `/qr/decode` dan `/barcode/decode` menjawab 200 dengan `count: 0` saat
    tidak ada barcode; itu bukan galat. Pakai `bytes_hex`/`bytes_base64` bila
    `text_is_binary` true atau `encoding_source` "guessed" dan teks tampak rusak.
22. Kirim URL berisi kredensial atau token ke `POST /url/parse`, bukan GET.

## Daftar endpoint

| Method | Path | Grup | Kegunaan | Status |
|---|---|---|---|---|
| GET | `/health` | Platform | Cek kesehatan server komputasi | aktif, tanpa token |
| POST | `/auth/register` | Auth | Daftar akun baru | aktif, tanpa token |
| POST | `/auth/login` | Auth | Login dengan email dan password | aktif, tanpa token |
| POST | `/auth/refresh` | Auth | Perbarui access token | aktif, tanpa token |
| POST | `/auth/logout` | Auth | Logout dan cabut sesi | aktif, tanpa token |
| GET | `/auth/me` | Auth | Identitas pemegang token | aktif |
| GET | `/me` | Akun | Profil akun | aktif |
| GET | `/me/quota` | Akun | Kuota harian dan rate limit | aktif |
| GET | `/me/onboarding` | Akun | Langkah onboarding akun | aktif |
| GET | `/usage` | Akun | Ringkasan penggunaan API | aktif |
| GET | `/logs` | Akun | Log permintaan (berhalaman) | aktif |
| GET | `/plans` | Akun | Daftar paket dan batasnya | aktif |
| GET | `/billing` | Akun | Ringkasan paket, kuota, dan transaksi | aktif |
| GET | `/billing/transactions` | Akun | Riwayat transaksi dompet | aktif |
| GET | `/tokens` | Token | Daftar API key aktif | aktif |
| POST | `/tokens` | Token | Buat API key baru | aktif |
| PATCH | `/tokens/{id}` | Token | Ubah nama, masa berlaku, atau cakupan API key | aktif |
| DELETE | `/tokens/{id}` | Token | Cabut API key | aktif |
| POST | `/tokens/{id}/rotate` | Token | Rotasi nilai API key | aktif |
| GET | `/apis` | Katalog | Katalog produk API | aktif, tanpa token |
| GET | `/apis/{slug}` | Katalog | Detail satu produk API | aktif, tanpa token |
| GET | `/wallet` | Dompet | Saldo coin | aktif |
| GET | `/credits` | Dompet | Saldo coin dan kredit layanan | aktif |
| POST | `/credits/buy` | Dompet | Tukar coin jadi kredit SMS/AI | aktif |
| GET | `/credits/transactions` | Dompet | Riwayat kredit | aktif |
| GET | `/wallet/transactions` | Dompet | Mutasi saldo | aktif |
| GET | `/wallet/methods` | Dompet | Metode pembayaran isi saldo | aktif |
| GET | `/wallet/topups` | Dompet | Riwayat permintaan isi saldo | aktif |
| POST | `/wallet/topup` | Dompet | Buat permintaan isi saldo | aktif |
| GET | `/wallet/topup/{id}` | Dompet | Status satu isi saldo | aktif |
| POST | `/wallet/topup/{id}/cancel` | Dompet | Batalkan isi saldo yang belum dibayar | aktif |
| POST | `/vouchers/redeem` | Voucher | Tukarkan kode voucher | aktif |
| GET | `/vouchers/history` | Voucher | Riwayat penukaran voucher | aktif |
| GET | `/tickets/categories` | Tiket | Kategori tiket dukungan | aktif |
| GET | `/tickets` | Tiket | Daftar tiket milik saya | aktif |
| POST | `/tickets` | Tiket | Buat tiket dukungan | aktif |
| GET | `/tickets/{id}` | Tiket | Detail tiket dan percakapannya | aktif |
| POST | `/tickets/{id}/messages` | Tiket | Balas tiket | aktif |
| POST | `/tickets/{id}/close` | Tiket | Tutup tiket | aktif |
| GET | `/blog/posts` | Blog | Daftar artikel blog yang terbit | aktif, tanpa token |
| GET | `/blog/posts/{slug}` | Blog | Baca satu artikel blog | aktif, tanpa token |
| GET | `/admin/blog` | Blog Admin | Admin: daftar semua artikel | aktif |
| POST | `/admin/blog` | Blog Admin | Admin: buat artikel (draf) | aktif |
| GET | `/admin/blog/{id}` | Blog Admin | Admin: detail artikel | aktif |
| PATCH | `/admin/blog/{id}` | Blog Admin | Admin: ubah artikel | aktif |
| DELETE | `/admin/blog/{id}` | Blog Admin | Admin: hapus artikel | aktif |
| POST | `/admin/blog/media` | Blog Admin | Admin: unggah gambar blog | aktif |
| POST | `/admin/blog/{id}/publish` | Blog Admin | Admin: terbitkan, jadwalkan, atau turunkan | aktif |
| GET | `/admin/blog/authors/me` | Blog Admin | Admin: profil penulis saya | aktif |
| PUT | `/admin/blog/authors/me` | Blog Admin | Admin: simpan profil penulis | aktif |
| POST | `/wa/messages/text` | WhatsApp | Kirim pesan teks WhatsApp | segera hadir |
| GET | `/wa/messages/{id}` | WhatsApp | Cek status pengiriman pesan | segera hadir |
| POST | `/sms/send` | SMS | Kirim SMS | aktif |
| GET | `/sms/types` | SMS | Jenis pesan SMS yang aktif | aktif |
| GET | `/sms/balance` | SMS | Saldo kredit SMS | aktif |
| GET | `/sms/messages` | SMS | Riwayat SMS terkirim | aktif |
| GET | `/ai/catalog` | AI | Katalog model AI (publik) | aktif, tanpa token |
| GET | `/ai/models` | AI | Daftar model AI | aktif |
| POST | `/ai/chat/completions` | AI | Chat completions (format OpenAI) | aktif |
| POST | `/ai/messages` | AI | Messages (format Anthropic) | aktif |
| POST | `/ai/embeddings` | AI | Embeddings (format OpenAI) | aktif |
| POST | `/ai/images/generations` | AI | Buat gambar (format OpenAI) | aktif |
| POST | `/ai/audio/speech` | AI | Text-to-speech (format OpenAI) | aktif |
| POST | `/ai/audio/transcriptions` | AI | Transkripsi audio (format OpenAI) | aktif |
| POST | `/ai/rerank` | AI | Rerank dokumen | aktif |
| GET | `/ai/videos/options` | AI | Opsi model video + perkiraan harga per detik | aktif |
| POST | `/ai/videos` | AI | Buat video (asinkron) | aktif |
| GET | `/ai/videos/{id}` | AI | Status job video | aktif |
| GET | `/ai/videos/{id}/content` | AI | Unduh video (MP4) | aktif |
| POST | `/qr/generate` | QR | Buat QR code dari teks, URL, atau konten terstruktur | aktif |
| POST | `/qr/decode` | QR | Baca QR code (QR, Micro QR, rMQR) dari gambar | aktif |
| POST | `/toolkits/gmail/send` | Gmail | Kirim email lewat akun Gmail yang terhubung | aktif |
| GET | `/toolkits/gmail/messages` | Gmail | Ambil daftar email dari akun Gmail yang terhubung | aktif |
| GET | `/toolkits/gmail/messages/{id}` | Gmail | Ambil isi satu email Gmail | aktif |
| GET | `/toolkits/googlecalendar/events` | Google Calendar | Daftar event Google Calendar | aktif |
| POST | `/toolkits/googlecalendar/events` | Google Calendar | Buat event di Google Calendar | aktif |
| POST | `/toolkits/googledocs/documents` | Google Workspace | Buat dokumen Google Docs | aktif |
| GET | `/toolkits/googledocs/documents/{documentId}/pdf` | Dokumen | Ekspor dokumen Google Docs ke PDF | aktif |
| POST | `/toolkits/googlesheets/spreadsheets` | Google Workspace | Buat spreadsheet Google Sheets | aktif |
| GET | `/toolkits/googledrive/files` | Google Workspace | Daftar berkas di Google Drive | aktif |
| POST | `/toolkits/googlemeet/spaces` | Google Workspace | Buat ruang pertemuan Google Meet | aktif |
| GET | `/toolkits/linkedin/me` | LinkedIn | Ambil profil akun LinkedIn yang terhubung | aktif |
| POST | `/toolkits/linkedin/posts` | LinkedIn | Buat postingan di LinkedIn | aktif |
| POST | `/toolkits/github/issues` | GitHub | Buat issue di repositori GitHub | aktif |
| POST | `/pdf/html` | Dokumen | Render HTML atau URL publik menjadi PDF | aktif |
| GET | `/telegram/bot/me` | Telegram Bot | Identitas bot dan pengecekan token | aktif |
| POST | `/telegram/bot/me` | Telegram Bot | Identitas bot (token lewat body) | aktif |
| POST | `/telegram/bot/messages` | Telegram Bot | Kirim pesan teks ke chat | aktif |
| POST | `/telegram/bot/photo` | Telegram Bot | Kirim foto lewat URL publik atau file_id | aktif |
| POST | `/telegram/bot/document` | Telegram Bot | Kirim dokumen lewat URL publik atau file_id | aktif |
| GET | `/telegram/bot/updates` | Telegram Bot | Ambil update terbaru (long polling) | aktif |
| GET | `/telegram/bot/webhook` | Telegram Bot | Status webhook bot | aktif |
| POST | `/telegram/bot/webhook` | Telegram Bot | Pasang webhook update bot | aktif |
| DELETE | `/telegram/bot/webhook` | Telegram Bot | Lepas webhook bot | aktif |
| POST | `/telegram/account/login/start` | Telegram Account | Langkah 1 - minta kode OTP ke akun | aktif |
| POST | `/telegram/account/login/verify` | Telegram Account | Langkah 2 - verifikasi kode OTP | aktif |
| POST | `/telegram/account/login/password` | Telegram Account | Langkah 3 - kata sandi verifikasi dua langkah | aktif |
| GET | `/telegram/account/session` | Telegram Account | Status session akun (tanpa session string) | aktif |
| DELETE | `/telegram/account/session` | Telegram Account | Cabut session dan logout di Telegram | aktif |
| POST | `/telegram/account/messages` | Telegram Account | Kirim pesan sebagai pengguna | aktif |
| GET | `/telegram/account/dialogs` | Telegram Account | Daftar chat milik akun | aktif |
| GET | `/track/emails` | Email Tracking | Daftar pelacak email | aktif |
| POST | `/track/emails` | Email Tracking | Buat pelacak email (dapatkan URL piksel) | aktif |
| GET | `/track/emails/{id}` | Email Tracking | Status buka satu email | aktif |
| PATCH | `/track/emails/{id}` | Email Tracking | Ubah pelacak email | aktif |
| DELETE | `/track/emails/{id}` | Email Tracking | Hapus pelacak email | aktif |
| POST | `/track/emails/{id}/events/{eventId}/exclude` | Email Tracking | Kecualikan satu event ("Itu saya") | aktif |
| POST | `/track/emails/{id}/events/{eventId}/include` | Email Tracking | Hitung kembali event yang dikecualikan | aktif |
| GET | `/track/open/{id}.gif` | Email Tracking | Piksel pelacak (publik, disematkan di email) | aktif, tanpa token |
| POST | `/crypto/hash` | Keamanan | Hash data dengan algoritma pilihan | aktif |
| POST | `/otp/enroll` | Authenticator | Buat secret, URI otpauth://, dan QR enrolment | aktif |
| POST | `/otp/verify` | Authenticator | Verifikasi kode TOTP atau HOTP | aktif |
| POST | `/otp/backup-codes` | Authenticator | Terbitkan kode cadangan sekali pakai beserta hash-nya | aktif |
| POST | `/translate` | Translator | Terjemahkan teks atau array teks | aktif |
| GET | `/translate/languages` | Translator | Daftar bahasa dan provider Translator | aktif |
| POST | `/tts` | Text to Speech | Ubah teks menjadi audio MP3 | aktif |
| GET | `/tts/voices` | Text to Speech | Daftar voice Text to Speech | aktif |
| GET | `/tts/servers` | Text to Speech | Daftar server Text to Speech | aktif |
| GET | `/ip/lookup/{ip}` | Lokasi | Cari detail satu alamat IP | aktif |
| GET | `/wilayah/provinsi` | Wilayah | Daftar seluruh provinsi | aktif |
| GET | `/wilayah/kabupaten/{kodeProvinsi}` | Wilayah | Daftar kabupaten dan kota dalam satu provinsi | aktif |
| GET | `/wilayah/kecamatan/{kodeKabupaten}` | Wilayah | Daftar kecamatan dalam satu kabupaten atau kota | aktif |
| GET | `/wilayah/kodepos/{kodepos}` | Wilayah | Cari wilayah berdasarkan kode pos | aktif |
| GET | `/nik/parse/{nik}` | Identitas | Uraikan satu NIK menjadi data terstruktur | aktif |
| GET | `/tanggal/hari-ini` | Tanggal | Tanggal hari ini dalam beberapa kalender | aktif |
| GET | `/tanggal/selisih` | Tanggal | Hitung selisih dua tanggal | aktif |
| GET | `/sholat/harian` | Sholat | Jadwal sholat satu hari | aktif |
| POST | `/ocr/general` | OCR | Ekstraksi teks dari gambar | aktif |
| POST | `/ocr/ktp` | OCR | Ekstraksi field KTP dari foto | aktif |
| POST | `/ocr/sim` | OCR | Ekstraksi field SIM dari foto | aktif |
| GET | `/maps/geocode` | Maps | Alamat menjadi koordinat | aktif |
| GET | `/maps/reverse` | Maps | Koordinat menjadi alamat | aktif |
| GET | `/libur/tahun/{tahun}` | Hari Libur | Daftar hari libur nasional satu tahun | aktif |
| GET | `/libur/cek/{tanggal}` | Hari Libur | Cek satu tanggal libur atau bukan | aktif |
| GET | `/libur/berikutnya` | Hari Libur | Hari libur nasional terdekat | aktif |
| GET | `/libur/hari-kerja` | Hari Libur | Hitung jumlah hari kerja antara dua tanggal | aktif |
| POST | `/barcode/generate` | Barcode | Buat barcode 1D/2D sebagai gambar PNG atau SVG | aktif |
| POST | `/barcode/decode` | Barcode | Baca semua barcode 1D/2D dari gambar, lengkap dengan byte mentah | aktif |
| GET | `/url/parse` | URL | Urai URL menjadi komponennya (query string) | aktif |
| POST | `/url/parse` | URL | Urai URL menjadi komponennya (body JSON) | aktif |
| GET | `/report` | Laporan | Daftar laporan milik akun ini | aktif |
| POST | `/report` | Laporan | Kirim laporan masalah atau masukan ke tim APIPedia | aktif |
| GET | `/report/{id}` | Laporan | Cek status satu laporan | aktif |
| GET | `/connections` | Koneksi | Daftar semua koneksi akun pihak ketiga | aktif |
| POST | `/connections/{toolkit}/initiate` | Koneksi | Mulai proses hubungkan akun pihak ketiga | aktif |
| GET | `/connections/{toolkit}/status` | Koneksi | Cek status koneksi satu toolkit | aktif |
| POST | `/connections/{id}/resume` | Koneksi | Lanjutkan otorisasi akun yang masih menunggu | aktif |
| DELETE | `/connections/{toolkit}` | Koneksi | Putuskan koneksi satu toolkit | aktif |
| GET | `/connections/{toolkit}/callback` | Koneksi | Callback OAuth (internal, dipanggil penyedia) | internal |
| POST | `/dl/{platform}/info` | Downloader | Ambil metadata media dari platform pihak ketiga | aktif |
| POST | `/dl/{platform}/fetch` | Downloader | Ambil tautan unduhan media dari platform pihak ketiga | aktif |
| GET | `/ongkir/couriers` | Ongkir | Daftar kurir dan layanan yang didukung | aktif |
| GET | `/ongkir/search` | Ongkir | Cari id kabupaten atau kecamatan | aktif |
| GET | `/ongkir/cek` | Ongkir | Hitung estimasi ongkos kirim | aktif |
| GET | `/gempa/terkini` | Gempa | Gempa terakhir yang tercatat BMKG | aktif |
| GET | `/gempa/m5` | Gempa | Daftar gempa magnitudo 5 ke atas | aktif |
| GET | `/gempa/dirasakan` | Gempa | Daftar gempa yang dirasakan masyarakat | aktif |
| GET | `/cuaca` | Cuaca | Prakiraan cuaca satu kelurahan/desa | aktif |
| GET | `/kodepos/cari` | Kode Pos | Cari kode pos dari nama tempat | aktif |
| GET | `/kodepos/{kodepos}` | Kode Pos | Semua kecamatan yang memakai satu kode pos | aktif |
| POST | `/qris/parse` | QRIS | Uraikan payload QRIS dan periksa CRC | aktif |
| GET | `/pddikti/all` | PDDikti | Pencarian gabungan PDDikti | aktif |
| GET | `/pddikti/mahasiswa` | PDDikti | Cari mahasiswa | aktif |
| GET | `/pddikti/mahasiswa/{id}` | PDDikti | Detail mahasiswa | aktif |
| GET | `/pddikti/dosen` | PDDikti | Cari dosen | aktif |
| GET | `/pddikti/dosen/{id}` | PDDikti | Detail dosen | aktif |
| GET | `/pddikti/pt` | PDDikti | Cari perguruan tinggi | aktif |
| GET | `/pddikti/pt/{id}` | PDDikti | Detail perguruan tinggi | aktif |
| GET | `/pddikti/prodi` | PDDikti | Cari program studi | aktif |
| GET | `/pddikti/prodi/{id}` | PDDikti | Detail program studi | aktif |
| GET | `/kurs/latest` | Kurs | Kurs terbaru terhadap satu mata uang dasar | aktif |
| GET | `/kurs/convert` | Kurs | Konversi nominal antar dua mata uang | aktif |
| GET | `/kurs/currencies` | Kurs | Daftar mata uang yang didukung | aktif |
| GET | `/kampus` | Kampus | Daftar universitas dengan halaman | aktif |
| GET | `/kampus/cari` | Kampus | Cari universitas berdasarkan nama atau domain | aktif |
| GET | `/rumah-sakit/cari` | Rumah Sakit | Cari rumah sakit berdasarkan nama atau provinsi | aktif |
| GET | `/sekolah/cari` | Sekolah | Cari sekolah berdasarkan nama, NPSN, provinsi, kabupaten, atau jenjang | aktif |
| GET | `/npwp/parse/{npwp}` | NPWP | Uraikan struktur satu NPWP | aktif |
| GET | `/rekening/banks` | Rekening | Daftar bank dan kode kliringnya | aktif |
| POST | `/rekening/validate` | Rekening | Periksa format satu nomor rekening | aktif |
| POST | `/vision/background/remove` | Vision | Hapus latar belakang satu gambar | aktif |
| POST | `/vision/face/match` | Vision | Cocokkan dua foto wajah | aktif |
| POST | `/vision/face/liveness` | Vision | Periksa keaslian wajah | aktif |
| GET | `/mail/detect` | Email | Tebak server IMAP/SMTP dari alamat email | aktif |
| GET | `/mail/accounts` | Email | Daftar mailbox terhubung | aktif |
| POST | `/mail/accounts` | Email | Hubungkan mailbox IMAP/SMTP | aktif |
| POST | `/mail/accounts/{id}/test` | Email | Uji ulang login IMAP dan SMTP | aktif |
| GET | `/mail/folders` | Email | Daftar folder mailbox | aktif |
| GET | `/mail/messages` | Email | Daftar dan cari email | aktif |
| GET | `/mail/messages/{uid}` | Email | Baca satu email | aktif |
| PATCH | `/mail/messages/{uid}` | Email | Tandai dibaca/belum dibaca atau berbintang | aktif |
| DELETE | `/mail/messages/{uid}` | Email | Pindahkan email ke Sampah | aktif |
| GET | `/mail/messages/{uid}/attachments/{index}` | Email | Unduh lampiran email | aktif |
| POST | `/mail/send` | Email | Kirim atau balas email | aktif |
| POST | `/mail/webhooks` | Email | Buat inbox webhook (URL rahasia) | aktif |
| POST | `/mail/webhooks/{id}/rotate` | Email | Ganti URL inbox webhook | aktif |
| POST | `/mail-hook/{secret}` | Email | Terima email ke inbox webhook (publik) | internal |
| GET | `/drive/providers` | Cloud Drive | Daftar penyedia dan field kredensial | aktif |
| GET | `/drive/accounts` | Cloud Drive | Daftar akun drive terhubung | aktif |
| POST | `/drive/accounts` | Cloud Drive | Hubungkan akun penyimpanan (form kredensial) | aktif |
| POST | `/drive/oauth/{provider}/start` | Cloud Drive | Mulai login OAuth Dropbox/OneDrive/Yandex Disk | aktif |
| GET | `/drive/files` | Cloud Drive | Isi folder | aktif |
| DELETE | `/drive/files` | Cloud Drive | Hapus berkas atau folder | aktif |
| GET | `/drive/download` | Cloud Drive | Unduh berkas | aktif |
| POST | `/drive/upload` | Cloud Drive | Unggah berkas | aktif |
| POST | `/drive/folders` | Cloud Drive | Buat folder | aktif |
| POST | `/drive/rename` | Cloud Drive | Ganti nama berkas atau folder | aktif |
| GET | `/drive/quota` | Cloud Drive | Kuota penyimpanan | aktif |
| GET | `/drive/search` | Cloud Drive | Cari berkas di semua akun | aktif |
| GET | `/drive-oauth/{provider}/callback` | Cloud Drive | Callback OAuth Dropbox/OneDrive/Yandex Disk (internal) | internal |

# Referensi endpoint

Setiap bagian memuat method, URL lengkap, parameter, body, contoh curl, dan
contoh respons. Nilai contoh bersifat ilustratif; bentuk kuncinya mengikuti
handler server.

## WhatsApp

WAPI — kirim pesan WhatsApp (segera hadir)

### POST /wa/messages/text

**Kirim pesan teks WhatsApp**

- URL: `POST https://api.apipedia.id/v1/wa/messages/text`
- **BELUM AKTIF** (segera hadir) - jangan dipanggil
- Auth: `Authorization: Bearer <token>`
- operationId: `sendWhatsappText`

Mengirim satu pesan teks ke nomor tujuan lewat perangkat WhatsApp yang terhubung.

Body JSON:

```json
{
  "to": "628123456789",
  "message": "Halo, pesanan kamu sudah diproses."
}
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pesan WhatsApp masuk antrean pengiriman",
  "data": {
    "message_id": "wa_9f1a2b3c",
    "to": "628123456789",
    "status": "queued",
    "sent_at": "2026-09-13T04:12:00Z"
  }
}
```

### GET /wa/messages/{id}

**Cek status pengiriman pesan**

- URL: `GET https://api.apipedia.id/v1/wa/messages/{id}`
- **BELUM AKTIF** (segera hadir) - jangan dipanggil
- Auth: `Authorization: Bearer <token>`
- operationId: `getWhatsappMessageStatus`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: wa_9f1a2b3c |

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berhasil",
  "data": {
    "message_id": "wa_9f1a2b3c",
    "status": "delivered",
    "delivered_at": "2026-09-13T04:12:04Z"
  }
}
```

## SMS

SMS Gateway: kirim SMS ke nomor Indonesia, dibayar dengan kredit SMS (dibeli dengan coin lewat POST /credits/buy).

### POST /sms/send

**Kirim SMS**

- URL: `POST https://api.apipedia.id/v1/sms/send`
- Auth: `Authorization: Bearer <token>`
- operationId: `sendSms`

Mengirim satu SMS ke nomor seluler Indonesia dan memotong kredit SMS sesuai jenis pesannya (lihat GET /sms/types). Bisa dipanggil dengan API key `apk_...` (cakupan produk `sms-gateway`) atau JWT console.
Body: `to` wajib, nomor Indonesia `08...`, `628...`, atau `+628...` (dinormalkan ke awalan `0`); `message` wajib, 1-480 karakter; `type` opsional, salah satu jenis yang aktif (bawaan `otp`): `otp` = SMS OTP (45 kredit/pesan), `sender` = SMS reguler (30 kredit/pesan). Harga per jenis diatur admin; nilai terkini ada di GET /sms/types.
Kredit dipotong SEBELUM pesan diteruskan ke provider. Bila provider menolak atau tidak menjawab (timeout 20 detik), kredit dikembalikan otomatis (refund) dan respons 502. `balance` di respons adalah sisa kredit SMS setelah pengiriman.
Tips agar tingkat keberhasilan tinggi (anjuran WebSMS): hindari kata Token, OTP, Kode, Verifikasi, Validasi, Registrasi (dan padanan Inggrisnya: code, verification, validation, registration) di isi pesan. Contoh yang aman: "Angka masuk kamu 482913. Berlaku 5 menit, jangan bagikan ke siapa pun."
Error: 400 nomor/isi/jenis tidak valid; 402 "Kredit SMS tidak cukup. Beli kredit di menu Saldo." (beli kredit dengan coin lewat POST /credits/buy); 502 provider gagal (kredit sudah dikembalikan); 503 "SMS gateway belum dikonfigurasi."

Body JSON:

```json
{
  "to": "081234567890",
  "message": "Angka masuk kamu 482913. Berlaku 5 menit, jangan bagikan ke siapa pun.",
  "type": "otp"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/sms/send" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":"081234567890","message":"Angka masuk kamu 482913. Berlaku 5 menit, jangan bagikan ke siapa pun.","type":"otp"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "SMS terkirim",
  "data": {
    "id": "6b1f2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
    "type": "otp",
    "to": "081234567890",
    "credits": 45,
    "status": "sent",
    "balance": 955
  }
}
```

### GET /sms/types

**Jenis pesan SMS yang aktif**

- URL: `GET https://api.apipedia.id/v1/sms/types`
- Auth: `Authorization: Bearer <token>`
- operationId: `listSmsTypes`

Jenis pesan yang boleh dipakai di field `type` POST /sms/send beserta jumlah kredit SMS yang terpotong per pesan. Hanya jenis yang diaktifkan admin yang muncul (bawaan: `otp` SMS OTP 45 kredit dan `sender` SMS reguler 30 kredit).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/sms/types" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Jenis SMS berhasil diambil",
  "data": {
    "items": [
      {
        "id": "otp",
        "label": "SMS OTP",
        "credits": 45
      },
      {
        "id": "sender",
        "label": "SMS Reguler",
        "credits": 30
      }
    ]
  }
}
```

### GET /sms/balance

**Saldo kredit SMS**

- URL: `GET https://api.apipedia.id/v1/sms/balance`
- Auth: `Authorization: Bearer <token>`
- operationId: `getSmsBalance`

Sisa kredit SMS pemanggil dan kredit per jenis pesan yang aktif. Kredit SMS dibeli dengan coin lewat POST /credits/buy (atau menu Saldo di console).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/sms/balance" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Saldo kredit SMS berhasil diambil",
  "data": {
    "balance": 955,
    "creditsPerType": {
      "otp": 45,
      "sender": 30
    }
  }
}
```

### GET /sms/messages

**Riwayat SMS terkirim**

- URL: `GET https://api.apipedia.id/v1/sms/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `listSmsMessages`

50 SMS terbaru milik pemanggil, terbaru dulu. `status` bernilai `sent` atau `failed` (kredit pesan yang gagal sudah dikembalikan).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/sms/messages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat SMS berhasil diambil",
  "data": {
    "items": [
      {
        "id": "6b1f2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "type": "otp",
        "to": "081234567890",
        "message": "Angka masuk kamu 482913. Berlaku 5 menit, jangan bagikan ke siapa pun.",
        "credits": 45,
        "status": "sent",
        "error": null,
        "created_at": "2026-10-29T03:15:00Z"
      }
    ]
  }
}
```

## Telegram Bot

Telegram Bot API resmi dengan bot token milik pemanggil (header X-Telegram-Bot-Token): kirim teks, foto, dokumen, long polling update, dan webhook.

### GET /telegram/bot/me

**Identitas bot dan pengecekan token**

- URL: `GET https://api.apipedia.id/v1/telegram/bot/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotGetMe`

Meneruskan `getMe` ke Bot API resmi Telegram: memastikan bot token masih berlaku dan mengembalikan identitas bot. Panggilan termurah untuk mengetes token baru.
Dua kredensial berbeda: `Authorization: Bearer apk_...` (token APIPedia, wajib seperti endpoint lain) dan bot token dari @BotFather lewat header `X-Telegram-Bot-Token`. Bot token tidak disimpan APIPedia dan tidak pernah ikut di log. Varian `POST /telegram/bot/me` (identik) tersedia supaya token bisa dikirim lewat field `bot_token` di body.
`data` adalah objek `User` Telegram apa adanya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/telegram/bot/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Identitas bot berhasil diambil.",
  "data": {
    "id": 123456789,
    "is_bot": true,
    "first_name": "APIPedia Notifier",
    "username": "apipedia_notifier_bot",
    "can_join_groups": true,
    "can_read_all_group_messages": false,
    "supports_inline_queries": false
  }
}
```

### POST /telegram/bot/me

**Identitas bot (token lewat body)**

- URL: `POST https://api.apipedia.id/v1/telegram/bot/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotGetMePost`

Sama persis dengan `GET /telegram/bot/me`, tetapi bot token boleh dikirim lewat field `bot_token` di body (memudahkan uji coba dari Playground). Header `X-Telegram-Bot-Token` tetap didahulukan bila keduanya ada.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON (opsional):

```json
{
  "bot_token": "123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/bot/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"bot_token":"123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Identitas bot berhasil diambil.",
  "data": {
    "id": 123456789,
    "is_bot": true,
    "first_name": "APIPedia Notifier",
    "username": "apipedia_notifier_bot",
    "can_join_groups": true,
    "can_read_all_group_messages": false,
    "supports_inline_queries": false
  }
}
```

### POST /telegram/bot/messages

**Kirim pesan teks ke chat**

- URL: `POST https://api.apipedia.id/v1/telegram/bot/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotSendMessage`

Meneruskan `sendMessage` ke Bot API. Body: `chat_id` wajib (id numerik chat, atau `@username` channel/grup publik); `text` wajib, maks 4096 karakter (lebih dari itu ditolak 400, pecah menjadi beberapa panggilan). Opsional dan diteruskan apa adanya bila diisi: `parse_mode` (`HTML`, `MarkdownV2`, `Markdown`), `disable_notification`, `protect_content`, `reply_to_message_id`, `message_thread_id`, `link_preview_options`, `reply_markup`, `entities`. Field lain diabaikan.
Bot hanya bisa mengirim ke pengguna yang sudah memulai chat dengan bot (/start) atau ke grup/channel tempat bot menjadi anggota. `data` adalah objek `Message` Telegram apa adanya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON:

```json
{
  "chat_id": 987654321,
  "text": "Pesanan <b>#INV-2026-001</b> sudah dikirim.",
  "parse_mode": "HTML"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/bot/messages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":987654321,"text":"Pesanan <b>#INV-2026-001</b> sudah dikirim.","parse_mode":"HTML"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pesan berhasil dikirim.",
  "data": {
    "message_id": 42,
    "from": {
      "id": 123456789,
      "is_bot": true,
      "first_name": "APIPedia Notifier",
      "username": "apipedia_notifier_bot"
    },
    "chat": {
      "id": 987654321,
      "first_name": "Rani",
      "username": "rani_pratama",
      "type": "private"
    },
    "date": 1793000000,
    "text": "Pesanan #INV-2026-001 sudah dikirim.",
    "entities": [
      {
        "offset": 8,
        "length": 13,
        "type": "bold"
      }
    ]
  }
}
```

### POST /telegram/bot/photo

**Kirim foto lewat URL publik atau file_id**

- URL: `POST https://api.apipedia.id/v1/telegram/bot/photo`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotSendPhoto`

Meneruskan `sendPhoto` ke Bot API. Body: `chat_id` wajib; `photo` wajib, berupa URL http/https yang bisa diakses publik (Telegram sendiri yang mengunduhnya) atau `file_id` milik bot ini. Unggahan berkas langsung (multipart), path lokal, dan data URI tidak diterima. Opsional: `caption`, `parse_mode`, `disable_notification`, `protect_content`, `reply_to_message_id`, `message_thread_id`, `reply_markup`.
`data` adalah objek `Message` Telegram; `photo` berisi beberapa ukuran, dan `file_id`-nya bisa dipakai ulang tanpa mengunggah lagi.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON:

```json
{
  "chat_id": 987654321,
  "photo": "https://example.com/bukti-transfer.jpg",
  "caption": "Bukti transfer diterima"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/bot/photo" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":987654321,"photo":"https://example.com/bukti-transfer.jpg","caption":"Bukti transfer diterima"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Foto berhasil dikirim.",
  "data": {
    "message_id": 43,
    "from": {
      "id": 123456789,
      "is_bot": true,
      "first_name": "APIPedia Notifier",
      "username": "apipedia_notifier_bot"
    },
    "chat": {
      "id": 987654321,
      "first_name": "Rani",
      "username": "rani_pratama",
      "type": "private"
    },
    "date": 1793000060,
    "photo": [
      {
        "file_id": "AgACAgUAAxkDAAIBK2Zx-small",
        "file_unique_id": "AQADsmall",
        "file_size": 1834,
        "width": 90,
        "height": 47
      },
      {
        "file_id": "AgACAgUAAxkDAAIBK2Zx-large",
        "file_unique_id": "AQADlarge",
        "file_size": 48213,
        "width": 1200,
        "height": 630
      }
    ],
    "caption": "Bukti transfer diterima"
  }
}
```

### POST /telegram/bot/document

**Kirim dokumen lewat URL publik atau file_id**

- URL: `POST https://api.apipedia.id/v1/telegram/bot/document`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotSendDocument`

Meneruskan `sendDocument` ke Bot API. Body: `chat_id` wajib; `document` wajib, berupa URL http/https publik atau `file_id` milik bot ini (unggahan multipart belum didukung). Opsional: `caption`, `parse_mode`, `disable_notification`, `protect_content`, `reply_to_message_id`, `message_thread_id`, `reply_markup`.
Telegram membatasi berkas yang dikirim lewat URL (sekitar 20 MB, dan hanya tipe tertentu seperti PDF dan ZIP untuk dokumen); penolakan dari Telegram dijawab 400 dengan `data.telegram`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON:

```json
{
  "chat_id": "@kanal_pengumuman",
  "document": "https://example.com/laporan-oktober-2026.pdf",
  "caption": "Laporan bulanan Oktober 2026"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/bot/document" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":"@kanal_pengumuman","document":"https://example.com/laporan-oktober-2026.pdf","caption":"Laporan bulanan Oktober 2026"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Dokumen berhasil dikirim.",
  "data": {
    "message_id": 44,
    "sender_chat": {
      "id": -1001234567890,
      "title": "Kanal Pengumuman",
      "username": "kanal_pengumuman",
      "type": "channel"
    },
    "chat": {
      "id": -1001234567890,
      "title": "Kanal Pengumuman",
      "username": "kanal_pengumuman",
      "type": "channel"
    },
    "date": 1793000120,
    "document": {
      "file_name": "laporan-oktober-2026.pdf",
      "mime_type": "application/pdf",
      "file_id": "BQACAgUAAxkDAAIBLGZx-doc",
      "file_unique_id": "AgADdoc",
      "file_size": 245760
    },
    "caption": "Laporan bulanan Oktober 2026"
  }
}
```

### GET /telegram/bot/updates

**Ambil update terbaru (long polling)**

- URL: `GET https://api.apipedia.id/v1/telegram/bot/updates`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotGetUpdates`

Meneruskan `getUpdates` ke Bot API. Telegram menahan koneksi sampai ada update atau `timeout` detik berlalu (long polling; maks 50 detik di gateway ini). Setelah memproses update, panggil lagi dengan `offset` = `update_id` terakhir + 1 supaya update lama tidak dikirim ulang.
Tidak bisa dipakai selama webhook terpasang (Telegram menolak dengan 409, dijawab 400 di sini); lepas dulu lewat `DELETE /telegram/bot/webhook`. `data` adalah array `Update` Telegram apa adanya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |
| `offset` | query | integer | tidak | Bilangan bulat: `update_id` terakhir + 1. contoh: 812345679 |
| `limit` | query | integer | tidak | Jumlah update maksimum, 1-100 (bawaan Telegram 100). contoh: 20 |
| `timeout` | query | integer | tidak | Lama long polling dalam detik, 0-50. Bawaan 0 (langsung kembali). default: 0; contoh: 25 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/telegram/bot/updates?offset=812345679&limit=20&timeout=25" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Update terbaru berhasil diambil.",
  "data": [
    {
      "update_id": 812345679,
      "message": {
        "message_id": 45,
        "from": {
          "id": 987654321,
          "is_bot": false,
          "first_name": "Rani",
          "username": "rani_pratama",
          "language_code": "id"
        },
        "chat": {
          "id": 987654321,
          "first_name": "Rani",
          "username": "rani_pratama",
          "type": "private"
        },
        "date": 1793000180,
        "text": "/start",
        "entities": [
          {
            "offset": 0,
            "length": 6,
            "type": "bot_command"
          }
        ]
      }
    }
  ]
}
```

### GET /telegram/bot/webhook

**Status webhook bot**

- URL: `GET https://api.apipedia.id/v1/telegram/bot/webhook`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotGetWebhook`

Meneruskan `getWebhookInfo` ke Bot API. `url` kosong berarti webhook tidak terpasang (bot memakai long polling). `last_error_date`/`last_error_message` muncul bila Telegram gagal mengirim update ke webhook Anda. `data` adalah objek `WebhookInfo` Telegram apa adanya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/telegram/bot/webhook" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Informasi webhook berhasil diambil.",
  "data": {
    "url": "https://example.com/telegram/webhook",
    "has_custom_certificate": false,
    "pending_update_count": 0,
    "max_connections": 40,
    "ip_address": "203.0.113.10"
  }
}
```

### POST /telegram/bot/webhook

**Pasang webhook update bot**

- URL: `POST https://api.apipedia.id/v1/telegram/bot/webhook`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotSetWebhook`

Meneruskan `setWebhook` ke Bot API: Telegram akan mengirim setiap update bot ke `url` milik Anda (langsung dari Telegram, tidak lewat APIPedia). Body: `url` wajib dan HARUS https (http ditolak 400 karena Telegram tidak pernah memanggilnya). Opsional: `secret_token` (dikirim balik Telegram di header `X-Telegram-Bot-Api-Secret-Token` untuk verifikasi), `allowed_updates`, `drop_pending_updates`, `max_connections`, `ip_address`.
`data` bernilai `true` bila berhasil. Selama webhook terpasang, `GET /telegram/bot/updates` tidak bisa dipakai.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON:

```json
{
  "url": "https://example.com/telegram/webhook",
  "secret_token": "rahasia-webhook-anda-123",
  "allowed_updates": [
    "message",
    "callback_query"
  ],
  "drop_pending_updates": true
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/bot/webhook" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/telegram/webhook","secret_token":"rahasia-webhook-anda-123","allowed_updates":["message","callback_query"],"drop_pending_updates":true}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Webhook berhasil dipasang.",
  "data": true
}
```

### DELETE /telegram/bot/webhook

**Lepas webhook bot**

- URL: `DELETE https://api.apipedia.id/v1/telegram/bot/webhook`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramBotDeleteWebhook`

Meneruskan `deleteWebhook` ke Bot API, supaya bot kembali bisa memakai long polling (`GET /telegram/bot/updates`). Body opsional: `drop_pending_updates` (boolean) untuk membuang update yang belum terkirim. `data` bernilai `true` bila berhasil.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `X-Telegram-Bot-Token` | header | string | tidak | Bot token dari @BotFather, berbentuk `123456789:AA...`. Wajib dikirim lewat header ini ATAU field `bot_token` di body (header didahulukan; untuk endpoint GET hanya header). Query string tidak diterima. Ini token bot Anda sendiri, bukan token APIPedia `apk_...`. contoh: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw |

Body JSON (opsional):

```json
{
  "drop_pending_updates": false
}
```

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/telegram/bot/webhook" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "X-Telegram-Bot-Token: 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw" \
  -H "Content-Type: application/json" \
  -d '{"drop_pending_updates":false}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Webhook berhasil dilepas.",
  "data": true
}
```

## Telegram Account

Otomatisasi AKUN Telegram pribadi lewat MTProto (login OTP bertahap). Melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API bila memungkinkan.

### POST /telegram/account/login/start

**Langkah 1 - minta kode OTP ke akun**

- URL: `POST https://api.apipedia.id/v1/telegram/account/login/start`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountLoginStart`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Langkah pertama login MTProto: Telegram mengirim kode ke aplikasi Telegram di perangkat lain atau lewat SMS (Telegram yang memilih; lihat `dikirim_ke`). Body: `api_id` (angka) dan `api_hash` (32 karakter heksadesimal) milik Anda sendiri dari my.telegram.org, bukan milik APIPedia; `phone_number` dalam format internasional, mis. `+628123456789`.
`login_id` dipakai di langkah berikutnya dan berlaku 15 menit (`kedaluwarsa_pada`). Login yang sedang berjalan hanya hidup di memori server, jadi bisa hilang bila server dimulai ulang; ulangi dari langkah ini bila `/login/verify` menjawab 404. `phone_code_hash` dari Telegram tidak pernah dikirim ke klien, dan nomor telepon dikembalikan dalam bentuk tersamar.

Body JSON:

```json
{
  "api_id": 1234567,
  "api_hash": "0123456789abcdef0123456789abcdef",
  "phone_number": "+628123456789"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/account/login/start" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"api_id":1234567,"api_hash":"0123456789abcdef0123456789abcdef","phone_number":"+628123456789"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kode login sudah dikirim Telegram. Lanjutkan ke POST /login/verify. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "login_id": "b7e2c1d4-9a3f-4e6b-8c5d-1f2a3b4c5d6e",
    "dikirim_ke": "aplikasi Telegram",
    "phone_number": "+62812****789",
    "kedaluwarsa_pada": "2026-10-30T03:15:00.000Z",
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### POST /telegram/account/login/verify

**Langkah 2 - verifikasi kode OTP**

- URL: `POST https://api.apipedia.id/v1/telegram/account/login/verify`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountLoginVerify`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Body: `login_id` dari langkah 1; `code` kode yang dikirim Telegram; `account_label` opsional (nama akun di halaman Koneksi, bawaan username atau nama depan).
DUA hasil sukses yang harus ditangani klien: (1) login selesai - `butuh_password: false`, akun tersimpan sebagai koneksi `telegram-account` dan `connection_id` dikembalikan (contoh di bawah); (2) akun memakai verifikasi dua langkah - `data` hanya `{ butuh_password: true, peringatan }`, lanjutkan ke `POST /telegram/account/login/password` dengan `login_id` yang sama.
Session string hasil login disimpan terenkripsi AES-256-GCM dan TIDAK PERNAH dikembalikan ke klien; yang keluar hanya sidik jarinya (`session_fingerprint`).

Body JSON:

```json
{
  "login_id": "b7e2c1d4-9a3f-4e6b-8c5d-1f2a3b4c5d6e",
  "code": "12345",
  "account_label": "Akun CS"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/account/login/verify" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"login_id":"b7e2c1d4-9a3f-4e6b-8c5d-1f2a3b4c5d6e","code":"12345","account_label":"Akun CS"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Akun Telegram berhasil terhubung. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "butuh_password": false,
    "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
    "account": {
      "id": "5512345678",
      "username": "rani_pratama",
      "first_name": "Rani",
      "phone_number": "+62812****789"
    },
    "session_fingerprint": "sha256:3f9a1c7e5b2d8a40",
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### POST /telegram/account/login/password

**Langkah 3 - kata sandi verifikasi dua langkah**

- URL: `POST https://api.apipedia.id/v1/telegram/account/login/password`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountLoginPassword`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Hanya dipanggil bila `POST /telegram/account/login/verify` menjawab `butuh_password: true`. Body: `login_id` yang sama, `password` kata sandi verifikasi dua langkah (diverifikasi lewat SRP dan tidak pernah disimpan), `account_label` opsional. Jawaban suksesnya sama dengan login yang selesai di langkah 2.

Body JSON:

```json
{
  "login_id": "b7e2c1d4-9a3f-4e6b-8c5d-1f2a3b4c5d6e",
  "password": "kata-sandi-2fa-anda"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/account/login/password" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"login_id":"b7e2c1d4-9a3f-4e6b-8c5d-1f2a3b4c5d6e","password":"kata-sandi-2fa-anda"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Akun Telegram berhasil terhubung. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "butuh_password": false,
    "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
    "account": {
      "id": "5512345678",
      "username": "rani_pratama",
      "first_name": "Rani",
      "phone_number": "+62812****789"
    },
    "session_fingerprint": "sha256:3f9a1c7e5b2d8a40",
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### GET /telegram/account/session

**Status session akun (tanpa session string)**

- URL: `GET https://api.apipedia.id/v1/telegram/account/session`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountGetSession`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Daftar akun Telegram yang terhubung milik pemanggil, terbaru dulu. Session string TIDAK PERNAH ikut; yang ada hanya sidik jarinya, identitas akun, nomor tersamar, `terhubung_pada`, dan `terakhir_dipakai` (null bila belum pernah dipakai). `items` kosong berarti belum ada akun yang terhubung.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/telegram/account/session" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status session Telegram berhasil diambil. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "items": [
      {
        "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
        "account": {
          "id": "5512345678",
          "username": "rani_pratama",
          "first_name": "Rani",
          "phone_number": "+62812****789"
        },
        "session_fingerprint": "sha256:3f9a1c7e5b2d8a40",
        "terhubung_pada": "2026-10-30T03:02:11.000Z",
        "terakhir_dipakai": "2026-10-30T04:20:45.000Z"
      }
    ],
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### DELETE /telegram/account/session

**Cabut session dan logout di Telegram**

- URL: `DELETE https://api.apipedia.id/v1/telegram/account/session`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountDeleteSession`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Logout di sisi Telegram lebih dulu, lalu menghapus koneksi beserta session terenkripsinya dari APIPedia. Bila logout di Telegram gagal (mis. session sudah dicabut dari perangkat lain), data di APIPedia tetap dihapus dan `dicabut_di_telegram` bernilai false - cabut juga dari Telegram > Settings > Devices.
Body opsional: `connection_id`. Wajib diisi bila lebih dari satu akun terhubung (tanpa itu dijawab 409).

Body JSON (opsional):

```json
{
  "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97"
}
```

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/telegram/account/session" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"connection_id":"5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Session Telegram berhasil dicabut. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
    "dicabut_di_telegram": true,
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### POST /telegram/account/messages

**Kirim pesan sebagai pengguna**

- URL: `POST https://api.apipedia.id/v1/telegram/account/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountSendMessage`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Mengirim pesan SEBAGAI akun pribadi Anda (bukan bot) - bagian inilah yang paling berisiko membuat akun dibatasi atau diblokir, terutama untuk pesan massal ke orang yang tidak mengenal Anda.
Body: `peer` wajib (`@username`, nomor telepon yang ada di kontak, atau id numerik chat); `text` wajib, maks 4096 karakter; `parse_mode` opsional (`html` atau `md`; nilai lain diabaikan); `reply_to_message_id` opsional; `connection_id` opsional, wajib bila lebih dari satu akun terhubung.

Body JSON:

```json
{
  "peer": "@rani_pratama",
  "text": "Halo Rani, laporan bulan ini sudah saya kirim ke email.",
  "parse_mode": "md"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/telegram/account/messages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"peer":"@rani_pratama","text":"Halo Rani, laporan bulan ini sudah saya kirim ke email.","parse_mode":"md"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pesan berhasil dikirim sebagai akun Anda. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
    "message_id": 1873,
    "tanggal": "2026-10-30T04:20:45.000Z",
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

### GET /telegram/account/dialogs

**Daftar chat milik akun**

- URL: `GET https://api.apipedia.id/v1/telegram/account/dialogs`
- Auth: `Authorization: Bearer <token>`
- operationId: `telegramAccountListDialogs`

PERINGATAN JUJUR: endpoint ini mengendalikan AKUN Telegram pribadi lewat MTProto, bukan bot. Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen; pakai Telegram Bot API (/telegram/bot/*) bila memungkinkan. Setiap jawaban sukses membawa peringatan yang sama di `message` dan `data.peringatan`.
Daftar chat (dialog) akun, urut seperti di aplikasi Telegram. Per item: `id` (string), `nama`, `tipe` (`user`, `group`, atau `channel`), dan `pesan_belum_dibaca`. Isi pesan tidak ikut.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `limit` | query | integer | tidak | Jumlah chat, 1-100. default: 50; contoh: 20 |
| `connection_id` | query | string | tidak | Id koneksi akun Telegram. Wajib bila lebih dari satu akun terhubung. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/telegram/account/dialogs?limit=20" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar chat berhasil diambil. PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan.",
  "data": {
    "connection_id": "5f0c9a2e-7b1d-4c3e-9a8f-2d6b4e1c0a97",
    "items": [
      {
        "id": "987654321",
        "nama": "Rani Pratama",
        "tipe": "user",
        "pesan_belum_dibaca": 2
      },
      {
        "id": "-4012345678",
        "nama": "Tim Operasional",
        "tipe": "group",
        "pesan_belum_dibaca": 0
      },
      {
        "id": "-1009876543210",
        "nama": "Kanal Pengumuman",
        "tipe": "channel",
        "pesan_belum_dibaca": 14
      }
    ],
    "peringatan": "PERINGATAN: Otomatisasi akun pribadi melanggar Ketentuan Layanan Telegram dan berisiko akun diblokir permanen. Gunakan Telegram Bot API bila memungkinkan."
  }
}
```

## AI

AI Gateway kompatibel SDK OpenAI dan Anthropic: chat (dengan streaming), messages, embeddings, gambar, suara, transkripsi, rerank, video (asinkron). Auth `Authorization: Bearer apk_...` atau `x-api-key: apk_...`. Respons memakai format provider, tanpa amplop.

### GET /ai/catalog

**Katalog model AI (publik)**

- URL: `GET https://api.apipedia.id/v1/ai/catalog`
- Auth: tidak perlu token
- operationId: `getAiCatalog`

Katalog model AI yang aktif beserta harganya, untuk ditampilkan ke pengguna (halaman Katalog > Model AI di konsol). Publik, tanpa token, memakai amplop standar { is_success, message, data }; di-cache 60 detik.
`coinPerCredit`: 1 kredit AI = sekian coin = biaya 1 USD di provider. `freeDailyRequests`: batas request harian gabungan untuk model `free: true`. Per model: `id` (public id untuk field `model`), `format` (`openai` atau `anthropic`), `endpoint` yang dipakai, `modality`, `contextLength`, serta `pricing.usd` dan `pricing.coin` (`inputPer1m`, `outputPer1m` per 1 juta token, `perRequest` per panggilan; `null` bila tidak dikenakan). Untuk SDK OpenAI pakai GET /ai/models.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ai/catalog"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Katalog model AI berhasil diambil",
  "data": {
    "coinPerCredit": 17000,
    "freeDailyRequests": 10,
    "total": 3,
    "models": [
      {
        "id": "openai/gpt-4o-mini",
        "name": "GPT-4o mini",
        "provider": "OpenAI",
        "format": "openai",
        "endpoint": "POST /v1/ai/chat/completions",
        "modality": "text",
        "contextLength": 128000,
        "free": false,
        "pinned": true,
        "pricing": {
          "usd": {
            "inputPer1m": 0.15,
            "outputPer1m": 0.6,
            "perRequest": null
          },
          "coin": {
            "inputPer1m": 2550,
            "outputPer1m": 10200,
            "perRequest": null
          }
        }
      },
      {
        "id": "bansos/claude-haiku-4-5-20251001",
        "name": "Claude Haiku 4.5",
        "provider": "Anthropic",
        "format": "anthropic",
        "endpoint": "POST /v1/ai/messages",
        "modality": "text",
        "contextLength": 200000,
        "free": false,
        "pinned": false,
        "pricing": {
          "usd": {
            "inputPer1m": 1,
            "outputPer1m": 5,
            "perRequest": null
          },
          "coin": {
            "inputPer1m": 17000,
            "outputPer1m": 85000,
            "perRequest": null
          }
        }
      },
      {
        "id": "meta-llama/llama-3.3-70b-instruct:free",
        "name": "Llama 3.3 70B Instruct (free)",
        "provider": "OpenRouter",
        "format": "openai",
        "endpoint": "POST /v1/ai/chat/completions",
        "modality": "text",
        "contextLength": 131072,
        "free": true,
        "pinned": false,
        "pricing": {
          "usd": {
            "inputPer1m": 0,
            "outputPer1m": 0,
            "perRequest": null
          },
          "coin": {
            "inputPer1m": 0,
            "outputPer1m": 0,
            "perRequest": null
          }
        }
      }
    ]
  }
}
```

### GET /ai/models

**Daftar model AI**

- URL: `GET https://api.apipedia.id/v1/ai/models`
- Auth: `Authorization: Bearer <token>`
- operationId: `listAiModels`

Format OpenAI `GET /models` (tanpa amplop { is_success, message, data }) sehingga bisa dibaca SDK resmi (`client.models.list()`). Field `model` di semua endpoint AI WAJIB berisi `id` dari daftar ini (public id APIPedia), bukan nama model di provider.
Tambahan per model: `modality` (text, image, embedding, speech, transcription, rerank, video, audio), `context_length`, `pricing` (`input_per_1m`/`output_per_1m` per 1 juta token dan `request` per panggilan, dalam USD = kredit AI), dan `free`.
Model `free: true` bisa dipakai tanpa kredit, dibatasi sejumlah request per pengguna per hari (bawaan 10, gabungan semua model gratis, reset 00.00 WIB). Model berbayar memotong kredit AI (1 kredit = 1 USD biaya provider) yang dibeli dengan coin lewat POST /credits/buy.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ai/models" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "object": "list",
  "data": [
    {
      "id": "openai/gpt-4o-mini",
      "object": "model",
      "created": 1721172741,
      "owned_by": "apipedia",
      "modality": "text",
      "context_length": 128000,
      "pricing": {
        "input_per_1m": 0.15,
        "output_per_1m": 0.6,
        "request": 0,
        "credits_unit": "USD"
      },
      "free": false
    },
    {
      "id": "meta-llama/llama-3.3-70b-instruct:free",
      "object": "model",
      "created": 1733506137,
      "owned_by": "apipedia",
      "modality": "text",
      "context_length": 131072,
      "pricing": {
        "input_per_1m": 0,
        "output_per_1m": 0,
        "request": 0,
        "credits_unit": "USD"
      },
      "free": true
    }
  ]
}
```

### POST /ai/chat/completions

**Chat completions (format OpenAI)**

- URL: `POST https://api.apipedia.id/v1/ai/chat/completions`
- Auth: `Authorization: Bearer <token>`
- operationId: `createChatCompletion`

Kompatibel penuh dengan OpenAI Chat Completions: arahkan SDK OpenAI ke base URL `https://api.apipedia.id/v1/ai` dan pakai API key APIPedia sebagai `apiKey` (dikirim sebagai `Authorization: Bearer apk_...`). Respons adalah body provider apa adanya, TANPA amplop { is_success, message, data }.
`model` wajib berisi public id dari GET /ai/models; model yang tidak ada dijawab 404 `invalid_request_error` dengan pesan `Model "<x>" tidak tersedia. Lihat GET /v1/ai/models.`. Hanya model dari provider bertipe OpenAI/OpenRouter yang dilayani di sini; model Anthropic dipanggil lewat POST /ai/messages (400 bila tertukar).
**Streaming**: kirim `stream: true` untuk menerima Server-Sent Events (`data: {...}` per chunk, diakhiri `data: [DONE]`) yang diteruskan byte-per-byte dari provider. Pemakaian token dihitung dari chunk terakhir, jadi tagihan dicatat setelah stream selesai.
**Biaya**: model gratis tidak memotong kredit tetapi dihitung ke kuota harian (habis -> 429 `Kuota gratis hari ini habis (10 request). Top up kredit AI untuk lanjut.`). Model berbayar butuh saldo kredit AI > 0 (402 `Kredit AI habis. Beli kredit AI di menu Saldo.`); biayanya `usage.cost` dari provider bila ada, atau token x harga per 1M + harga per request.

Body JSON:

```json
{
  "model": "openai/gpt-4o-mini",
  "messages": [
    {
      "role": "system",
      "content": "Kamu asisten yang menjawab singkat dalam bahasa Indonesia."
    },
    {
      "role": "user",
      "content": "Ringkas paragraf berikut dalam satu kalimat: ..."
    }
  ],
  "temperature": 0.3,
  "stream": false
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/chat/completions" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-4o-mini","messages":[{"role":"system","content":"Kamu asisten yang menjawab singkat dalam bahasa Indonesia."},{"role":"user","content":"Ringkas paragraf berikut dalam satu kalimat: ..."}],"temperature":0.3,"stream":false}'
```

Contoh respons:

```json
{
  "id": "chatcmpl-AbC123",
  "object": "chat.completion",
  "created": 1761707700,
  "model": "openai/gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Berikut ringkasannya dalam satu kalimat."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 38,
    "completion_tokens": 12,
    "total_tokens": 50
  }
}
```

### POST /ai/messages

**Messages (format Anthropic)**

- URL: `POST https://api.apipedia.id/v1/ai/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `createAnthropicMessage`

Kompatibel dengan Anthropic Messages API. Dengan SDK Anthropic, set `baseURL` ke `https://api.apipedia.id/v1/ai` dan `apiKey` ke API key APIPedia: SDK mengirimnya sebagai header `x-api-key`, yang diterima gateway sama seperti `Authorization: Bearer`. Header `anthropic-version` boleh dikirim; gateway selalu memakai `2023-06-01` ke provider. Respons adalah body provider apa adanya (tanpa amplop).
`model` wajib berisi public id model ber-provider Anthropic dari GET /ai/models. `stream: true` mengembalikan SSE Anthropic (`message_start`, `content_block_delta`, `message_delta`, `message_stop`); pemakaian token dibaca dari `message_start` dan `message_delta` lalu ditagihkan setelah stream selesai.
Error memakai format Anthropic `{ type: "error", error: { type, message } }`: 402 kredit AI habis, 429 kuota gratis harian habis, 404 model tidak tersedia.

Body JSON:

```json
{
  "model": "bansos/claude-haiku-4-5-20251001",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": "Tulis tiga ide judul artikel tentang UMKM digital."
    }
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/messages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"bansos/claude-haiku-4-5-20251001","max_tokens":512,"messages":[{"role":"user","content":"Tulis tiga ide judul artikel tentang UMKM digital."}]}'
```

Contoh respons:

```json
{
  "id": "msg_01AbCdEf",
  "type": "message",
  "role": "assistant",
  "model": "bansos/claude-haiku-4-5-20251001",
  "content": [
    {
      "type": "text",
      "text": "1. UMKM Go Digital: ..."
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 22,
    "output_tokens": 64
  }
}
```

### POST /ai/embeddings

**Embeddings (format OpenAI)**

- URL: `POST https://api.apipedia.id/v1/ai/embeddings`
- Auth: `Authorization: Bearer <token>`
- operationId: `createEmbedding`

Kompatibel dengan OpenAI Embeddings (`client.embeddings.create`). `model` = public id model ber-modality `embedding` dari GET /ai/models. Respons body provider apa adanya. Biaya dihitung dari `usage.prompt_tokens` x harga input per 1M.

Body JSON:

```json
{
  "model": "openai/text-embedding-3-small",
  "input": [
    "Kota Bandung",
    "Ibu kota Jawa Barat"
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/embeddings" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/text-embedding-3-small","input":["Kota Bandung","Ibu kota Jawa Barat"]}'
```

Contoh respons:

```json
{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [
        0.0123,
        -0.0456,
        0.0789
      ]
    },
    {
      "object": "embedding",
      "index": 1,
      "embedding": [
        0.0111,
        -0.0422,
        0.0801
      ]
    }
  ],
  "model": "openai/text-embedding-3-small",
  "usage": {
    "prompt_tokens": 8,
    "total_tokens": 8
  }
}
```

### POST /ai/images/generations

**Buat gambar (format OpenAI)**

- URL: `POST https://api.apipedia.id/v1/ai/images/generations`
- Auth: `Authorization: Bearer <token>`
- operationId: `createImage`

Kompatibel dengan OpenAI Images (`client.images.generate`). `model` = public id model ber-modality `image`. Biaya memakai harga per request model (ditambah token bila provider melaporkannya). Respons body provider apa adanya (`url` atau `b64_json`).

Body JSON:

```json
{
  "model": "openai/gpt-image-1",
  "prompt": "Ilustrasi flat warung kopi di Yogyakarta saat senja",
  "size": "1024x1024"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/images/generations" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-image-1","prompt":"Ilustrasi flat warung kopi di Yogyakarta saat senja","size":"1024x1024"}'
```

Contoh respons:

```json
{
  "created": 1761707700,
  "data": [
    {
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA..."
    }
  ]
}
```

### POST /ai/audio/speech

**Text-to-speech (format OpenAI)**

- URL: `POST https://api.apipedia.id/v1/ai/audio/speech`
- Auth: `Authorization: Bearer <token>`
- operationId: `createSpeech`

Kompatibel dengan OpenAI Audio Speech (`client.audio.speech.create`). Respons berupa berkas audio biner (mis. `audio/mpeg`) yang diteruskan apa adanya dari provider, bukan JSON. `model` = public id model ber-modality `speech`. Biaya memakai harga per request model.

Body JSON:

```json
{
  "model": "elevenlabs/eleven-multilingual-v2",
  "input": "Selamat datang di APIPedia.",
  "voice": "alloy",
  "response_format": "mp3"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/audio/speech" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"elevenlabs/eleven-multilingual-v2","input":"Selamat datang di APIPedia.","voice":"alloy","response_format":"mp3"}'
```

Contoh respons:

```json
"<biner audio/mpeg>"
```

### POST /ai/audio/transcriptions

**Transkripsi audio (format OpenAI)**

- URL: `POST https://api.apipedia.id/v1/ai/audio/transcriptions`
- Auth: `Authorization: Bearer <token>`
- operationId: `createTranscription`

Kompatibel dengan OpenAI Audio Transcriptions (`client.audio.transcriptions.create`). Body `multipart/form-data` diteruskan apa adanya ke provider; hanya field `model` yang ditukar ke id model di provider. Field: `file` (berkas audio), `model` (public id ber-modality `transcription`), opsional `language` (mis. `id`) dan `response_format`.

Body JSON:

```json
{
  "file": "@rekaman.m4a",
  "model": "openai/whisper-1",
  "language": "id"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/audio/transcriptions" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"file":"@rekaman.m4a","model":"openai/whisper-1","language":"id"}'
```

Contoh respons:

```json
{
  "text": "Halo, ini rekaman uji coba transkripsi."
}
```

### POST /ai/rerank

**Rerank dokumen**

- URL: `POST https://api.apipedia.id/v1/ai/rerank`
- Auth: `Authorization: Bearer <token>`
- operationId: `rerankDocuments`

Mengurutkan `documents` menurut relevansinya terhadap `query`. Body `{ model, query, documents }` (plus field tambahan yang dikenali provider, mis. `top_n`) diteruskan apa adanya ke provider; `model` = public id ber-modality `rerank`. Respons body provider apa adanya.

Body JSON:

```json
{
  "model": "cohere/rerank-v3.5",
  "query": "cara bayar pajak kendaraan online",
  "documents": [
    "Samsat Digital Nasional (SIGNAL) ...",
    "Resep rendang padang ...",
    "e-Samsat via mobile banking ..."
  ],
  "top_n": 2
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/rerank" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"cohere/rerank-v3.5","query":"cara bayar pajak kendaraan online","documents":["Samsat Digital Nasional (SIGNAL) ...","Resep rendang padang ...","e-Samsat via mobile banking ..."],"top_n":2}'
```

Contoh respons:

```json
{
  "results": [
    {
      "index": 0,
      "relevance_score": 0.94
    },
    {
      "index": 2,
      "relevance_score": 0.88
    }
  ]
}
```

### GET /ai/videos/options

**Opsi model video + perkiraan harga per detik**

- URL: `GET https://api.apipedia.id/v1/ai/videos/options`
- Auth: `Authorization: Bearer <token>`
- operationId: `getVideoModelOptions`

Durasi, resolusi, rasio, dan dukungan gambar awal yang diterima satu model video, plus perkiraan biaya USD (= kredit AI) per detik per resolusi, dibaca dari katalog provider. Pakai sebelum POST /ai/videos supaya nilai yang dikirim pasti didukung.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `model` | query | string | ya | Public id model ber-modality `video` (lihat GET /ai/catalog). contoh: alibaba/happyhorse-1.0 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ai/videos/options?model=alibaba%2Fhappyhorse-1.0" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "model": "alibaba/happyhorse-1.0",
  "free": false,
  "known": true,
  "durations": [
    3,
    4,
    5,
    6,
    8,
    10,
    15
  ],
  "resolutions": [
    "720p",
    "1080p"
  ],
  "aspectRatios": [
    "16:9",
    "9:16",
    "1:1"
  ],
  "frameImages": [
    "first_frame"
  ],
  "audio": true,
  "perSecondUsd": {
    "720p": 0.0988,
    "1080p": 0.1694
  }
}
```

### POST /ai/videos

**Buat video (asinkron)**

- URL: `POST https://api.apipedia.id/v1/ai/videos`
- Auth: `Authorization: Bearer <token>`
- operationId: `createVideo`

Mengirim job pembuatan video dan langsung dijawab **202** dengan `id` job; video dibuat di latar (biasanya 1-5 menit). Polling `GET /ai/videos/{id}` sampai `status` = `completed`, lalu unduh MP4 dari `GET /ai/videos/{id}/content`.
Body: `model` (public id ber-modality `video`), `prompt` (maks 4.000 karakter), opsional `duration` (detik), `resolution` (mis. `720p`), `aspect_ratio` (mis. `16:9`), `size`, `generate_audio`, `seed`, `frame_images` (image-to-video: `[{ type: "image_url", image_url: { url }, frame_type: "first_frame" | "last_frame" }]`) dan `input_references` (gambar acuan). URL gambar harus `https://` atau `data:image/...;base64,` (maks ~8 MB, maks 4 gambar). Nilai yang tidak didukung model dijawab 400 beserta pilihannya (lihat GET /ai/videos/options).
**Biaya.** Video ditagih per detik. Saat dikirim, **perkiraan** biaya langsung ditahan dari kredit AI (atau dompet hadiah provider, mis. Bansos); saldo kurang dari perkiraan dijawab 402. Setelah selesai, tagihan disesuaikan ke biaya nyata provider: kelebihan dikembalikan, kekurangan ditagih. Job yang gagal, dibatalkan, atau kedaluwarsa mengembalikan seluruh tahanan. Model gratis (id berakhiran `:free`) memakai kuota harian model gratis.

Body JSON:

```json
{
  "model": "alibaba/happyhorse-1.0",
  "prompt": "Drone melintas pelan di atas sawah terasering saat matahari terbit, sinematik",
  "duration": 5,
  "resolution": "720p",
  "aspect_ratio": "16:9"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ai/videos" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"alibaba/happyhorse-1.0","prompt":"Drone melintas pelan di atas sawah terasering saat matahari terbit, sinematik","duration":5,"resolution":"720p","aspect_ratio":"16:9"}'
```

Contoh respons:

```json
{
  "id": "0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11",
  "object": "video.generation",
  "model": "alibaba/happyhorse-1.0",
  "status": "pending",
  "created_at": "2026-11-02T07:00:00Z",
  "completed_at": null,
  "polling_url": "/v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11",
  "content_urls": [],
  "error": null,
  "usage": {
    "estimate_usd": 0.494,
    "cost_usd": null,
    "credits_charged": 0.494,
    "is_free": false
  }
}
```

### GET /ai/videos/{id}

**Status job video**

- URL: `GET https://api.apipedia.id/v1/ai/videos/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getVideo`

Status: `pending`, `in_progress`, `completed`, `failed`, `cancelled`, `expired`. Saat `completed`, `content_urls` berisi alamat unduhan dan `usage` memuat biaya nyata (`cost_usd`) serta kredit yang ditagih (`credits_charged`); sebelum selesai `credits_charged` adalah tahanan perkiraan. Polling tiap 5-10 detik. Hanya pemilik job yang bisa membacanya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id job dari POST /ai/videos. contoh: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "id": "0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11",
  "object": "video.generation",
  "model": "alibaba/happyhorse-1.0",
  "status": "completed",
  "created_at": "2026-11-02T07:00:00Z",
  "completed_at": "2026-11-02T07:01:42Z",
  "polling_url": "/v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11",
  "content_urls": [
    "/v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11/content?index=0"
  ],
  "error": null,
  "usage": {
    "estimate_usd": 0.494,
    "cost_usd": 0.494,
    "credits_charged": 0.494,
    "is_free": false
  }
}
```

### GET /ai/videos/{id}/content

**Unduh video (MP4)**

- URL: `GET https://api.apipedia.id/v1/ai/videos/{id}/content`
- Auth: `Authorization: Bearer <token>`
- operationId: `getVideoContent`

Berkas video biner (`video/mp4`) dari job yang sudah `completed`, diambil lewat gateway dengan kunci provider milik server. Query opsional `index` (bawaan 0) bila model menghasilkan lebih dari satu video. Job yang belum selesai dijawab 409.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id job dari POST /ai/videos. contoh: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11 |
| `index` | query | integer | tidak | Urutan keluaran, bawaan 0. contoh: 0 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11/content?index=0" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
"<biner video/mp4>"
```

## QR

QR Code — buat dari teks, URL, vCard, WiFi, QRIS, dan lainnya; baca QR, Micro QR, dan rMQR dari gambar

### POST /qr/generate

**Buat QR code dari teks, URL, atau konten terstruktur**

- URL: `POST https://api.apipedia.id/v1/qr/generate`
- Auth: `Authorization: Bearer <token>`
- operationId: `generateQrCode`

Gambar PNG dikembalikan langsung sebagai data URL (`image_data_url`), bukan tautan CDN.
`content_type` menentukan cara isi dirakit: "wifi" butuh `ssid` (plus `password` dan `security`, default WPA) dan menghasilkan format WIFI:T:...;S:...;P:...;; yang dikenali pemindai; "vcard" butuh `name` (plus `phone`, `email`, `org` opsional) dan menghasilkan vCard 3.0; nilai lain ("text", "url", termasuk payload QRIS) memakai `text` apa adanya. `size` 64-2048 piksel (default 512). `error_correction` L, M (default), Q, atau H. Isi yang melebihi kapasitas QR (~2.900 karakter) dijawab 400.

Body JSON:

```json
{
  "content_type": "url",
  "text": "https://apipedia.id",
  "size": 512,
  "error_correction": "M"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/qr/generate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"url","text":"https://apipedia.id","size":512,"error_correction":"M"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "QR code berhasil dibuat",
  "data": {
    "content_type": "url",
    "format": "png",
    "size": 512,
    "error_correction": "M",
    "image_data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
  }
}
```

### POST /qr/decode

**Baca QR code (QR, Micro QR, rMQR) dari gambar**

- URL: `POST https://api.apipedia.id/v1/qr/decode`
- Auth: `Authorization: Bearer <token>`
- operationId: `decodeQrCode`

Sama seperti `POST /barcode/decode`, tetapi pencarian dibatasi ke QR Code, Micro QR, dan rMQR (field `formats` diabaikan). Body: `image_base64` wajib (base64 mentah atau data URL; PNG, JPEG, WebP, GIF, atau BMP; maksimal 8 MB), `try_harder` (default true), `max_symbols` 1-50 (default 10), dan `charset` opsional (label WHATWG apa pun, mis. shift_jis) untuk memaksa encoding saat mendekode byte.
Setiap simbol membawa `text` sekaligus byte mentahnya (`bytes_hex`, `bytes_base64`), jadi isi berencoding apa pun - termasuk biner - bisa dipulihkan utuh. ECI di simbol dihormati otomatis (`encoding_source: "eci"`); tanpa ECI charset ditebak (`guessed`); `charset` dari pemanggil selalu menang (`request`). `parsed` mengklasifikasi isi: url (lengkap dengan hasil /url/parse), wifi, vcard, mecard, email, tel, sms, geo, qris, binary, atau text.
Gambar tanpa QR dijawab 200 dengan `count: 0`, `symbols: []`, dan `hint`, bukan error. Error 400 hanya untuk gambar/base64 rusak, format gambar tak didukung, ukuran berlebih, `max_symbols` di luar rentang, atau charset tidak dikenal. Di-cache 24 jam (header `X-Cache`).

Body JSON:

```json
{
  "image_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0AQAAAAAVtjufAAABXElEQVR42u2XMZKcQAxFn4apGjL2Bs1NNDfxSbw0PlnvTeAGTcZUMXwHjO1kbYeiylYmRb9Q9+uHic9qufB5/XvzxczMnmZDO99f3SnzJ0kFvFl6wCXl2Jyj9cDHs4WfXfR3a6RMGq/R+/oR4Hll0PxlW4LzoJokFamYJld1SVn1bOccVQC2rrzfJm/qaxiXs5MkNRVberajC+VVLzOzN7c2jfbGt0ckr1Dl/Ta5qu/H2oAhdl+Xhx8ASOW63LnphDxHlWFNkjSuSeB76DlHFZMKnTJJTWUI54PaeVD90DobnS7MiuZSKqqeSaWRcjCXfu8JGfwKpskfZjspR787E6ouTQC2RvIBHbV1yuCNNK6xvHoZ53I3qXACXh2e2ZWv7Wx0hdB793f/hEfP0yyTzsF50wT4zpxP4MPzXS0ulVg//+WfkibfwNbo9+7wTGnFwffbGX3Y/v+//3H+HcDI5pRtrf/UAAAAAElFTkSuQmCC",
  "try_harder": true,
  "max_symbols": 10
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/qr/decode" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0AQAAAAAVtjufAAABXElEQVR42u2XMZKcQAxFn4apGjL2Bs1NNDfxSbw0PlnvTeAGTcZUMXwHjO1kbYeiylYmRb9Q9+uHic9qufB5/XvzxczMnmZDO99f3SnzJ0kFvFl6wCXl2Jyj9cDHs4WfXfR3a6RMGq/R+/oR4Hll0PxlW4LzoJokFamYJld1SVn1bOccVQC2rrzfJm/qaxiXs5MkNRVberajC+VVLzOzN7c2jfbGt0ckr1Dl/Ta5qu/H2oAhdl+Xhx8ASOW63LnphDxHlWFNkjSuSeB76DlHFZMKnTJJTWUI54PaeVD90DobnS7MiuZSKqqeSaWRcjCXfu8JGfwKpskfZjspR787E6ouTQC2RvIBHbV1yuCNNK6xvHoZ53I3qXACXh2e2ZWv7Wx0hdB793f/hEfP0yyTzsF50wT4zpxP4MPzXS0ulVg//+WfkibfwNbo9+7wTGnFwffbGX3Y/v+//3H+HcDI5pRtrf/UAAAAAElFTkSuQmCC","try_harder":true,"max_symbols":10}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "1 barcode berhasil dibaca",
  "data": {
    "count": 1,
    "symbols": [
      {
        "format": "QRCode",
        "format_label": "QR Code",
        "symbology": "QRCode",
        "text": "https://apipedia.id/blog?utm_source=qr",
        "content_type": "Text",
        "encoding": "ascii",
        "encoding_source": "guessed",
        "has_eci": false,
        "eci": null,
        "text_is_binary": false,
        "bytes_length": 38,
        "bytes_base64": "aHR0cHM6Ly9hcGlwZWRpYS5pZC9ibG9nP3V0bV9zb3VyY2U9cXI=",
        "bytes_hex": "68747470733a2f2f61706970656469612e69642f626c6f673f75746d5f736f757263653d7172",
        "position": {
          "topLeft": {
            "x": 30,
            "y": 30
          },
          "topRight": {
            "x": 204,
            "y": 30
          },
          "bottomRight": {
            "x": 204,
            "y": 204
          },
          "bottomLeft": {
            "x": 30,
            "y": 204
          }
        },
        "orientation": 0,
        "is_mirrored": false,
        "is_inverted": false,
        "symbology_identifier": "]Q1",
        "parsed": {
          "type": "url",
          "valid": true,
          "input": "https://apipedia.id/blog?utm_source=qr",
          "assumed_scheme": false,
          "normalized": "https://apipedia.id/blog?utm_source=qr",
          "protocol": "https:",
          "scheme": "https",
          "username": null,
          "has_password": false,
          "hostname": "apipedia.id",
          "hostname_unicode": "apipedia.id",
          "port": null,
          "default_port": 443,
          "effective_port": 443,
          "origin": "https://apipedia.id",
          "pathname": "/blog",
          "path_segments": [
            "blog"
          ],
          "search": "?utm_source=qr",
          "query": {
            "utm_source": "qr"
          },
          "hash": "",
          "fragment": null,
          "is_ip": false,
          "ip_version": null,
          "tld": "id",
          "domain": "apipedia.id",
          "subdomain": null,
          "is_icann": true,
          "is_private_suffix": false
        }
      }
    ],
    "formats_searched": [
      "QRCode",
      "MicroQRCode",
      "RMQRCode"
    ],
    "image": {
      "mime": "image/png",
      "bytes": 2391
    }
  }
}
```

## Barcode

Barcode — buat 12 simbologi 1D/2D (PNG/SVG) dan baca semua format umum dari gambar dengan byte mentah

### POST /barcode/generate

**Buat barcode 1D/2D sebagai gambar PNG atau SVG**

- URL: `POST https://api.apipedia.id/v1/barcode/generate`
- Auth: `Authorization: Bearer <token>`
- operationId: `generateBarcode`

Dirender di server dengan bwip-js (port BWIPP), latar putih plus quiet zone supaya langsung terbaca pemindai. Gambar dikembalikan langsung sebagai `image_data_url` dan `image_base64`, bukan tautan CDN.
`symbology` (default code128): 1D `code128` (ASCII penuh), `code39` (angka, A-Z kapital, spasi, - . $ / + %), `ean13`, `ean8`, `upca`, `upce`, `itf14`, `codabar` (otomatis diapit A...A bila tanpa karakter start/stop); 2D `qrcode`, `datamatrix`, `pdf417`, `azteccode`. `data` wajib, maksimal 4.096 byte UTF-8.
Digit periksa EAN-13/EAN-8/UPC-A/ITF-14: kirim satu digit lebih pendek (mis. 12 digit untuk EAN-13) supaya dihitung otomatis; kalau dikirim lengkap, digit periksanya diverifikasi dan yang salah dijawab 400 sambil menyebut digit yang benar. Field `data` di respons selalu berisi data akhir termasuk digit periksa.
Simbologi 1D hanya mengenal ASCII; teks Unicode (huruf beraksen, aksara non-Latin, emoji) dijawab 400 dan harus memakai simbologi 2D. Di simbologi 2D, teks non-ASCII dikodekan UTF-8 dengan penanda ECI 26 (`eci: "utf-8"` di respons) supaya pemindai tidak salah menebak charset; teks ASCII murni dibiarkan tanpa ECI (`eci: null`).
Ukuran: `format` png (default) atau svg; `scale` 1-20 (default 3, piksel per modul); `width`/`height` 32-4000 piksel diperlakukan sebagai BATAS ATAS - skala dipilih sebesar mungkin yang masih muat, dan ukuran aktualnya dilaporkan di respons. `include_text` (default true) menampilkan teks di bawah batang dan hanya berlaku untuk 1D. `error_correction` L, M, Q, atau H hanya untuk qrcode.
Error 400: simbologi tidak dikenal, data kosong/lebih dari 4.096 byte/berisi karakter terlarang, digit periksa salah, ukuran di luar rentang, atau data melebihi kapasitas satu simbol. Respons di-cache 24 jam (header `X-Cache`).

Body JSON:

```json
{
  "data": "899123456789",
  "symbology": "ean13",
  "format": "png",
  "scale": 3,
  "include_text": true
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/barcode/generate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"data":"899123456789","symbology":"ean13","format":"png","scale":3,"include_text":true}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Barcode berhasil dibuat",
  "data": {
    "symbology": "ean13",
    "format": "png",
    "data": "8991234567891",
    "width": 346,
    "height": 300,
    "scale": 3,
    "include_text": true,
    "eci": null,
    "image_data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVoAAAEsCAYAAACC...",
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAVoAAAEsCAYAAACC..."
  }
}
```

### POST /barcode/decode

**Baca semua barcode 1D/2D dari gambar, lengkap dengan byte mentah**

- URL: `POST https://api.apipedia.id/v1/barcode/decode`
- Auth: `Authorization: Bearer <token>`
- operationId: `decodeBarcode`

Membaca barcode dari gambar dengan zxing-cpp (WebAssembly di server, tanpa panggilan jaringan). Format yang dikenali: QR Code, Micro QR, rMQR, Data Matrix, Aztec, PDF417, MaxiCode, EAN-13, EAN-8, UPC-A, UPC-E, Code 128, Code 39, Code 93, ITF, Codabar, DataBar, dan DataBar Expanded. `formats` (opsional) membatasi pencarian, mis. `["EAN13", "Code128"]`; huruf besar-kecil dan pemisah diabaikan, nama simbologi /barcode/generate (`azteccode`, `codabar`) serta grup `AllLinear` dan `AllMatrix` juga diterima. Kosong = semua format.
Body: `image_base64` wajib (base64 mentah atau data URL; PNG, JPEG, WebP, GIF, atau BMP; maksimal 8 MB dan 40 megapiksel). `try_harder` (default true) mencoba rotasi dan skala lain. `max_symbols` 1-50 (default 10). `charset` opsional memaksa label encoding WHATWG (utf-8, shift_jis, euc-kr, gbk, gb18030, big5, iso-8859-1, windows-1252, utf-16le, dst.) saat mendekode byte.
Encoding universal: setiap simbol selalu membawa `bytes_hex` dan `bytes_base64` - byte mentah persis seperti tersimpan di simbol - jadi isi berencoding apa pun, termasuk biner, bisa dipulihkan tanpa kehilangan data. `text` adalah hasil dekode byte tersebut, dan `encoding_source` menyebut asal charset-nya: `eci` (penanda ECI di simbol, dihormati otomatis; `eci` berisi daftar nomornya), `request` (field `charset` menang atas ECI maupun tebakan), atau `guessed` (tebakan pembaca). `text_is_binary: true` berarti isinya bukan teks - pakai byte mentah. Untuk GS1, `text_hri` berisi bentuk "(01)...(17)..." dan `parsed.elements` berisi Application Identifier-nya.
`parsed.type` mengklasifikasi isi: url (lengkap dengan hasil /url/parse), wifi, vcard, mecard, email, tel, sms, geo, qris (diurai seperti /qris/parse), gs1, binary, atau text.
Gambar tanpa barcode BUKAN error: dijawab 200 dengan `count: 0`, `symbols: []`, dan `hint` berisi saran perbaikan gambar. Error 400 hanya untuk masukan rusak: base64 tidak valid, bukan gambar yang didukung, lebih dari 8 MB/40 megapiksel, gambar rusak, format tidak dikenal, `max_symbols` di luar rentang, atau charset tidak dikenal. Respons di-cache 24 jam (header `X-Cache`).

Body JSON:

```json
{
  "image_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVoAAAEsAQAAAACqUUYHAAACN0lEQVR42u3asW7UQBCA4X99FriAxB1IJIp5A+gCinT7CDwARURDkyKchESBklV0RWhISkRDCmoegGqDIuEmgjfAAj+Ac7rCB2sPxd0R8KWADsRMtVp9Gs3Maro1wm/HccQfhGLFihX/L7gZsJrmZjcpz+6sukkpyepgEh/YxuXlCDOYZHt5pqNTrFixYsWKFStWrFixYsWKFStWrFixYsWKFStWrFixYsWKFStWrFix4n8PIyLhkXRiZbdz8X5NRPw884gmY0hDkzbGQRPRcByTc+Y46pRx4MaV7DB24xo8BAjQyl0+etnu4KGvnrTLVFQbvQDQUtP/3C7zrmi7Dd4oevfZcgXX3oANAAZSttzV7dDFGWUa9jghgeBmbVDhOLS1644uowV/HWBCDVBCCY661838iQQDm7OUADnkPCXjShc/sDXz380lFaQEIHpGsfiC1hGBBUa8BRjz2HFoIkgueO6VKt4BeE3fxUeXC/CSAdt+AZ+4JmMIsM+GB5ZkrcDKEmduAb/0aRzhQFIiEkjIaGkfYtMulpvFEsYDXzcxAAIOwtCtnzcYzw/O7HMbwAHjb4CtcJOIS9kvmXvPwVakRMAEwIVpegweC6wXP9fswVHNL2xMjU8DAu0Fm3IoGeWsAfovGE0Lam1YmIb5QGvJgSZuBhbIzCkQb0zrOl8rERH6tciahN3CBERe1cvi+rUE5IvIwdT8WCtvDWRELiXqAdzCekPPEMO9GTL6N16xYsWK/0r8Hf6n+l5U9bSfAAAAAElFTkSuQmCC",
  "formats": [
    "QRCode",
    "EAN13"
  ],
  "try_harder": true,
  "max_symbols": 10
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/barcode/decode" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVoAAAEsAQAAAACqUUYHAAACN0lEQVR42u3asW7UQBCA4X99FriAxB1IJIp5A+gCinT7CDwARURDkyKchESBklV0RWhISkRDCmoegGqDIuEmgjfAAj+Ac7rCB2sPxd0R8KWADsRMtVp9Gs3Maro1wm/HccQfhGLFihX/L7gZsJrmZjcpz+6sukkpyepgEh/YxuXlCDOYZHt5pqNTrFixYsWKFStWrFixYsWKFStWrFixYsWKFStWrFixYsWKFStWrFix4n8PIyLhkXRiZbdz8X5NRPw884gmY0hDkzbGQRPRcByTc+Y46pRx4MaV7DB24xo8BAjQyl0+etnu4KGvnrTLVFQbvQDQUtP/3C7zrmi7Dd4oevfZcgXX3oANAAZSttzV7dDFGWUa9jghgeBmbVDhOLS1644uowV/HWBCDVBCCY661838iQQDm7OUADnkPCXjShc/sDXz380lFaQEIHpGsfiC1hGBBUa8BRjz2HFoIkgueO6VKt4BeE3fxUeXC/CSAdt+AZ+4JmMIsM+GB5ZkrcDKEmduAb/0aRzhQFIiEkjIaGkfYtMulpvFEsYDXzcxAAIOwtCtnzcYzw/O7HMbwAHjb4CtcJOIS9kvmXvPwVakRMAEwIVpegweC6wXP9fswVHNL2xMjU8DAu0Fm3IoGeWsAfovGE0Lam1YmIb5QGvJgSZuBhbIzCkQb0zrOl8rERH6tciahN3CBERe1cvi+rUE5IvIwdT8WCtvDWRELiXqAdzCekPPEMO9GTL6N16xYsWK/0r8Hf6n+l5U9bSfAAAAAElFTkSuQmCC","formats":["QRCode","EAN13"],"try_harder":true,"max_symbols":10}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "1 barcode berhasil dibaca",
  "data": {
    "count": 1,
    "symbols": [
      {
        "format": "QRCode",
        "format_label": "QR Code",
        "symbology": "QRCode",
        "text": "Halo, 日本語 ✓",
        "content_type": "Text",
        "encoding": "utf-8",
        "encoding_source": "eci",
        "has_eci": true,
        "eci": [
          {
            "eci": 26,
            "charset": "utf-8"
          }
        ],
        "text_is_binary": false,
        "bytes_length": 19,
        "bytes_base64": "SGFsbywg5pel5pys6KqeIOKckw==",
        "bytes_hex": "48616c6f2c20e697a5e69cace8aa9e20e29c93",
        "position": {
          "topLeft": {
            "x": 30,
            "y": 30
          },
          "topRight": {
            "x": 180,
            "y": 30
          },
          "bottomRight": {
            "x": 180,
            "y": 180
          },
          "bottomLeft": {
            "x": 30,
            "y": 180
          }
        },
        "orientation": 0,
        "is_mirrored": false,
        "is_inverted": false,
        "symbology_identifier": "]Q1",
        "parsed": {
          "type": "text"
        }
      }
    ],
    "formats_searched": [
      "QRCode",
      "EAN13"
    ],
    "image": {
      "mime": "image/png",
      "bytes": 1819
    }
  }
}
```

## URL

URL Parser — bedah URL (skema, host, TLD/domain public suffix, query, path) tanpa menghubunginya

### GET /url/parse

**Urai URL menjadi komponennya (query string)**

- URL: `GET https://api.apipedia.id/v1/url/parse`
- Auth: `Authorization: Bearer <token>`
- operationId: `parseUrlGet`

Membedah URL dengan parser WHATWG (sama seperti peramban) ditambah daftar public suffix (tldts) - tanpa pernah menghubungi URL-nya. Hasilnya: `scheme`, `hostname` dan `hostname_unicode` (IDN punycode didekode), `port`/`default_port`/`effective_port`, `origin`, `pathname` dan `path_segments` (sudah didekode persen), `query` sebagai objek (kunci berulang menjadi array), `fragment`, serta `tld` (public suffix, mis. co.id), `domain` terdaftar, `subdomain`, `is_icann`, dan `is_private_suffix`. Host berupa IP ditandai `is_ip` dan `ip_version` (4/6), tanpa tld/domain.
Masukan tanpa skema (`apipedia.id/blog`, `localhost:3000`, `//cdn.contoh.id`) diberi https:// dan ditandai `assumed_scheme: true`; teks tanpa skema yang tidak berbentuk domain, IP, atau localhost ditolak 400. Skema tanpa "//" seperti mailto:, tel:, dan geo: dikenali apa adanya. Kata sandi di userinfo TIDAK PERNAH digemakan: `has_password` menandai keberadaannya, dan `input`/`normalized` menampilkan `***`.
URL maksimal 8 KB. Respons di-cache 24 jam (header `X-Cache`), KECUALI permintaan GET yang URL-nya membawa kredensial (`@`) - supaya kata sandi tidak tersimpan sebagai kunci cache. Untuk URL berisi rahasia atau URL panjang, pakai `POST /url/parse`.
Error 400: `url` kosong, lebih dari 8 KB, tidak bisa diurai, atau teks tanpa skema yang tidak berbentuk domain.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `url` | query | string | ya | URL yang diurai, di-encode sebagai komponen query. Maksimal 8 KB. contoh: https://blog.apipedia.co.id:8443/artikel/cara-validasi-nik?tag=nik&tag=api#ringkasan |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/url/parse?url=https%3A%2F%2Fblog.apipedia.co.id%3A8443%2Fartikel%2Fcara-validasi-nik%3Ftag%3Dnik%26tag%3Dapi%23ringkasan" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "URL berhasil diurai",
  "data": {
    "valid": true,
    "input": "https://blog.apipedia.co.id:8443/artikel/cara-validasi-nik?tag=nik&tag=api#ringkasan",
    "assumed_scheme": false,
    "normalized": "https://blog.apipedia.co.id:8443/artikel/cara-validasi-nik?tag=nik&tag=api#ringkasan",
    "protocol": "https:",
    "scheme": "https",
    "username": null,
    "has_password": false,
    "hostname": "blog.apipedia.co.id",
    "hostname_unicode": "blog.apipedia.co.id",
    "port": 8443,
    "default_port": 443,
    "effective_port": 8443,
    "origin": "https://blog.apipedia.co.id:8443",
    "pathname": "/artikel/cara-validasi-nik",
    "path_segments": [
      "artikel",
      "cara-validasi-nik"
    ],
    "search": "?tag=nik&tag=api",
    "query": {
      "tag": [
        "nik",
        "api"
      ]
    },
    "hash": "#ringkasan",
    "fragment": "ringkasan",
    "is_ip": false,
    "ip_version": null,
    "tld": "co.id",
    "domain": "apipedia.co.id",
    "subdomain": "blog",
    "is_icann": true,
    "is_private_suffix": false
  }
}
```

### POST /url/parse

**Urai URL menjadi komponennya (body JSON)**

- URL: `POST https://api.apipedia.id/v1/url/parse`
- Auth: `Authorization: Bearer <token>`
- operationId: `parseUrlPost`

Sama persis dengan `GET /url/parse`, tetapi URL dikirim di body `{ "url": "..." }` sehingga tidak perlu di-encode ulang ke query string dan tidak ikut tercatat di log akses. Pilih bentuk ini untuk URL panjang atau URL yang membawa kredensial/token. Kata sandi userinfo tetap tidak pernah dikembalikan (`has_password: true`, `***` di `input`/`normalized`). Maksimal 8 KB; di-cache 24 jam (body hanya disimpan sebagai hash).
Error 400: `url` kosong, lebih dari 8 KB, tidak bisa diurai, atau teks tanpa skema yang tidak berbentuk domain.

Body JSON:

```json
{
  "url": "https://USER:PASSWORD@files.apipedia.id/unduh/laporan.pdf?token=abc123"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/url/parse" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://USER:PASSWORD@files.apipedia.id/unduh/laporan.pdf?token=abc123"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "URL berhasil diurai",
  "data": {
    "valid": true,
    "input": "https://USER:***@files.apipedia.id/unduh/laporan.pdf?token=abc123",
    "assumed_scheme": false,
    "normalized": "https://USER:***@files.apipedia.id/unduh/laporan.pdf?token=abc123",
    "protocol": "https:",
    "scheme": "https",
    "username": "USER",
    "has_password": true,
    "hostname": "files.apipedia.id",
    "hostname_unicode": "files.apipedia.id",
    "port": null,
    "default_port": 443,
    "effective_port": 443,
    "origin": "https://files.apipedia.id",
    "pathname": "/unduh/laporan.pdf",
    "path_segments": [
      "unduh",
      "laporan.pdf"
    ],
    "search": "?token=abc123",
    "query": {
      "token": "abc123"
    },
    "hash": "",
    "fragment": null,
    "is_ip": false,
    "ip_version": null,
    "tld": "id",
    "domain": "apipedia.id",
    "subdomain": "files",
    "is_icann": true,
    "is_private_suffix": false
  }
}
```

## Translator

Translator — terjemahkan teks atau array teks lewat Google, Google Dict, MyMemory (tanpa kunci, failover otomatis), atau model AI pilihan Anda (memotong kredit AI)

### POST /translate

**Terjemahkan teks atau array teks**

- URL: `POST https://api.apipedia.id/v1/translate`
- Auth: `Authorization: Bearer <token>`
- operationId: `translateText`

Menerjemahkan satu teks atau array teks ke bahasa `target`.
Body: `text` wajib — string maks 5.000 karakter, ATAU array string maks 50 item dengan total maks 5.000 karakter (item kosong dikembalikan apa adanya; `translatedText` ikut berbentuk array dengan urutan sama); `target` wajib, kode bahasa seperti `en`, `id`, `ja`, `zh-CN` (lihat GET /translate/languages; kode BCP-47 lain yang didukung provider juga diterima); `source` opsional, bawaan `auto` (dideteksi); `provider` opsional: `auto` (bawaan) | `google` | `google-dict` | `mymemory` | `ai`.
**Mode auto** mencoba `google` → `google-dict` → `mymemory`. Provider yang gagal (captcha Google `/sorry/`, halaman HTML, kuota harian MyMemory habis, timeout 10 detik) dilewati; `data.attempts` mencatat setiap percobaan beserta alasannya, dan `data.provider` adalah provider yang berhasil. Bila semuanya gagal: 502 dengan `data.attempts`. Memilih provider tertentu berarti tanpa failover. MyMemory dibatasi 500 byte per potongan, jadi teks panjang dipecah per kalimat lalu disambung ulang.
**Mode ai** (`provider: "ai"`) memakai AI Gateway dengan token Anda sendiri: `model` wajib (public id dari GET /ai/models), biaya dipotong dari kredit AI (model gratis memakai kuota harian model gratis), dan API key harus mencakup `ai-gateway`. Format, markdown, baris baru, dan placeholder (`{x}`, `{{nama}}`, `%s`) dipertahankan; instruksi yang tersisip di teks sumber tidak dijalankan, hanya diterjemahkan. Opsional: `formality` (`formal` | `informal`) dan `instructions` (maks 300 karakter, hanya gaya/istilah). Contoh:
```json
{ "text": ["Halo {name}, pesanan Anda sudah dikirim.", "Terima kasih!"], "target": "en", "provider": "ai", "model": "gpt-4o-mini", "formality": "formal" }
```
Jawaban: `{ "translatedText": ["Hello {name}, your order has been shipped.", "Thank you!"], "source": "id", "target": "en", "provider": "ai", "model": "gpt-4o-mini" }`. Bila model mengembalikan jumlah item berbeda dari masukan, jawabannya 502.
Cache: hasil provider non-AI di-cache 6 jam per body yang identik (header `X-Cache`). Mode ai TIDAK pernah di-cache (`Cache-Control: no-store`).

Body JSON:

```json
{
  "text": "Selamat pagi, apa kabar?",
  "target": "en",
  "source": "auto",
  "provider": "auto"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/translate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text":"Selamat pagi, apa kabar?","target":"en","source":"auto","provider":"auto"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Teks berhasil diterjemahkan (provider mymemory).",
  "data": {
    "translatedText": "Good morning, how are you?",
    "source": "id",
    "target": "en",
    "provider": "mymemory",
    "attempts": [
      {
        "provider": "google",
        "status": "failed",
        "reason": "Diblokir captcha Google (redirect ke /sorry/); IP server dianggap lalu lintas otomatis.",
        "ms": 700
      },
      {
        "provider": "google-dict",
        "status": "failed",
        "reason": "Diblokir captcha Google (redirect ke /sorry/); IP server dianggap lalu lintas otomatis.",
        "ms": 607
      },
      {
        "provider": "mymemory",
        "status": "success",
        "ms": 1052
      }
    ]
  }
}
```

### GET /translate/languages

**Daftar bahasa dan provider Translator**

- URL: `GET https://api.apipedia.id/v1/translate/languages`
- Auth: `Authorization: Bearer <token>`
- operationId: `translateLanguages`

Daftar statis 40 bahasa umum (kode, nama Indonesia, nama Inggris; `auto` hanya untuk `source`), provider yang tersedia beserta keterangannya, urutan failover mode auto, dan batas masukan. Kode BCP-47 lain yang didukung provider (mis. `pt-BR`) tetap diterima POST /translate.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/translate/languages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "40 bahasa umum dan 5 provider.",
  "data": {
    "languages": [
      {
        "code": "auto",
        "name": "Deteksi otomatis",
        "nameEn": "Detect language",
        "sourceOnly": true
      },
      {
        "code": "id",
        "name": "Indonesia",
        "nameEn": "Indonesian"
      },
      {
        "code": "en",
        "name": "Inggris",
        "nameEn": "English"
      },
      {
        "code": "jv",
        "name": "Jawa",
        "nameEn": "Javanese"
      },
      {
        "code": "zh-CN",
        "name": "Mandarin (Sederhana)",
        "nameEn": "Chinese (Simplified)"
      },
      {
        "code": "ja",
        "name": "Jepang",
        "nameEn": "Japanese"
      }
    ],
    "providers": [
      {
        "id": "auto",
        "name": "Otomatis (bawaan)",
        "requiresModel": false,
        "cached": true,
        "description": "Mencoba google, lalu google-dict, lalu mymemory. Provider yang gagal (captcha, kuota habis, timeout) dilewati; daftar percobaan ada di data.attempts."
      },
      {
        "id": "mymemory",
        "name": "MyMemory",
        "requiresModel": false,
        "cached": true,
        "description": "Memori terjemahan publik MyMemory. Kuota harian gratis terbatas; teks dipecah per kalimat (maks. 500 byte per potongan)."
      },
      {
        "id": "ai",
        "name": "AI Gateway",
        "requiresModel": true,
        "cached": false,
        "description": "Model AI pilihan Anda (field \"model\", lihat GET /v1/ai/models). Menjaga format, markdown, dan placeholder {x}; mendukung formality dan instructions. Memotong kredit AI dan butuh cakupan ai-gateway; tidak pernah dicache."
      }
    ],
    "defaultProvider": "auto",
    "autoOrder": [
      "google",
      "google-dict",
      "mymemory"
    ],
    "limits": {
      "maxChars": 5000,
      "maxItems": 50,
      "maxInstructions": 300
    },
    "note": "Daftar ini contoh bahasa yang umum dipakai. Kode BCP-47 lain yang didukung provider (mis. \"pt-BR\") juga diterima."
  }
}
```

## Text to Speech

Text to Speech — ubah teks menjadi audio MP3 dengan 300+ suara neural (Indonesia, Jawa, Sunda, dan 70+ bahasa lain), dua server yang bisa dipilih, plus waktu per kata dan subtitle SRT

### POST /tts

**Ubah teks menjadi audio MP3**

- URL: `POST https://api.apipedia.id/v1/tts`
- Auth: `Authorization: Bearer <token>`
- operationId: `ttsSpeak`

Mengubah teks menjadi suara neural (mesin Microsoft Edge TTS) dalam format MP3 24 kHz mono.
Body: `text` wajib, maks 5.000 karakter (teks panjang dipecah otomatis per kalimat dan audionya disambung); `voice` opsional, bawaan `id-ID-GadisNeural` (perempuan; `id-ID-ArdiNeural` laki-laki; daftar lengkap di GET /tts/voices); `rate` kecepatan -50 sampai 100 (persen relatif), `pitch` nada -50 sampai 50 (Hz relatif), `volume` -50 sampai 50 (persen relatif) — ketiganya menerima angka (`10`) atau teks (`"+10%"`, `"-5Hz"`), bawaan 0; `bitrate` `48` (bawaan) atau `96` kbps.
**Memilih server.** Ada dua server TTS: `server: 1` (bawaan) dan `server: 2`. Keduanya menghasilkan suara yang sama; bedanya hanya jalur jaringannya. Server yang dipakai selalu yang Anda minta — tidak ada perpindahan otomatis — dan tercantum di header `X-TTS-Server` (atau field `data.server`). Bila satu server menjawab 502/503/504, ulangi dengan server lainnya. Status tiap server ada di GET /tts/servers. `server` boleh dikirim di body atau sebagai query `?server=2`.
**Bentuk jawaban** (`output`, di body atau query):
- `audio` (bawaan): berkas `audio/mpeg` mentah, siap disimpan sebagai `.mp3` atau diputar langsung.
- `base64`: amplop JSON berisi `base64`, `size`, `voice`, `server`. Dengan `subtitles: true` (hanya pada output base64) ditambah `words` (waktu mulai/selesai tiap kata dalam milidetik) dan `srt` (subtitle siap pakai, 10 kata per baris).
Jawaban tidak pernah dicache (`Cache-Control: no-store`). Error: 400 masukan tidak valid; 502 server TTS gagal menyintesis (mis. nama voice tidak dikenal); 503 server yang dipilih sedang tidak tersedia atau sibuk (pesannya menyebut server lain yang bisa dicoba); 504 melewati batas waktu.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `output` | query | string | tidak | `audio` (bawaan, berkas audio/mpeg) atau `base64` (amplop JSON). Boleh juga dikirim di body. pilihan: audio, base64; default: audio; contoh: base64 |
| `server` | query | string | tidak | Server TTS: `1` (bawaan) atau `2`. Boleh juga dikirim di body. pilihan: 1, 2; default: 1; contoh: 1 |

Body JSON:

```json
{
  "text": "Halo, selamat datang di APIPedia. Ini contoh suara dari API Text to Speech.",
  "voice": "id-ID-GadisNeural",
  "rate": 0,
  "pitch": 0,
  "volume": 0,
  "bitrate": 48,
  "server": 1,
  "subtitles": true
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tts?output=base64&server=1" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text":"Halo, selamat datang di APIPedia. Ini contoh suara dari API Text to Speech.","voice":"id-ID-GadisNeural","rate":0,"pitch":0,"volume":0,"bitrate":48,"server":1,"subtitles":true}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Audio berhasil dibuat.",
  "data": {
    "server": 1,
    "voice": "id-ID-GadisNeural",
    "format": "mp3",
    "contentType": "audio/mpeg",
    "bitrate": 48,
    "size": 38880,
    "characters": 75,
    "base64": "//NkxAAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVV...",
    "words": [
      {
        "text": "Halo",
        "startMs": 100,
        "endMs": 512
      },
      {
        "text": "selamat",
        "startMs": 750,
        "endMs": 1187
      },
      {
        "text": "datang",
        "startMs": 1200,
        "endMs": 1562
      }
    ],
    "srt": "1\n00:00:00,100 --> 00:00:03,450\nHalo selamat datang di APIPedia Ini contoh suara dari API\n\n2\n00:00:03,462 --> 00:00:04,900\nText to Speech\n"
  }
}
```

### GET /tts/voices

**Daftar voice Text to Speech**

- URL: `GET https://api.apipedia.id/v1/tts/voices`
- Auth: `Authorization: Bearer <token>`
- operationId: `ttsVoices`

Daftar voice yang bisa dipakai di field `voice` POST /tts: nama (`name`), locale, gender, nama tampilan, serta kategori dan karakter suaranya. Saring dengan `locale` (kode bahasa `id` cocok dengan semua `id-*`; locale penuh `en-US` hanya cocok dengan dirinya), `gender` (`Male`/`Female`), dan `q` (kata kunci pada nama). Daftar di-cache 12 jam di server; jawaban juga memuat `servers` dan batas masukan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `locale` | query | string | tidak | Kode bahasa (`id`, `jv`, `su`, `en`) atau locale penuh (`id-ID`, `en-US`). contoh: id |
| `gender` | query | string | tidak | `Male` atau `Female`. pilihan: Male, Female; contoh: Female |
| `q` | query | string | tidak | Kata kunci pada nama voice, mis. `gadis` atau `multilingual`. contoh: gadis |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tts/voices?locale=id&gender=Female&q=gadis" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "1 voice ditemukan.",
  "data": {
    "count": 1,
    "total": 322,
    "defaultVoice": "id-ID-GadisNeural",
    "servers": [
      {
        "id": 1,
        "name": "Server 1",
        "default": true,
        "available": true
      },
      {
        "id": 2,
        "name": "Server 2",
        "default": false,
        "available": true
      }
    ],
    "voices": [
      {
        "name": "id-ID-GadisNeural",
        "locale": "id-ID",
        "gender": "Female",
        "displayName": "Gadis - Indonesian (Indonesia)",
        "categories": [
          "General"
        ],
        "personalities": [
          "Friendly",
          "Positive"
        ]
      }
    ],
    "limits": {
      "maxChars": 5000,
      "bitrates": [
        48,
        96
      ],
      "rate": [
        -50,
        100
      ],
      "pitch": [
        -50,
        50
      ],
      "volume": [
        -50,
        50
      ]
    }
  }
}
```

### GET /tts/servers

**Daftar server Text to Speech**

- URL: `GET https://api.apipedia.id/v1/tts/servers`
- Auth: `Authorization: Bearer <token>`
- operationId: `ttsServers`

Daftar server TTS yang bisa dipilih lewat field `server` POST /tts. `default: true` menandai server yang dipakai bila `server` tidak dikirim; `available: false` berarti server itu sedang dimatikan dan permintaan ke sana dijawab 503. Kedua server menghasilkan suara yang sama — pilih bebas, dan pindah ke server lain bila salah satunya lambat atau gagal.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tts/servers" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "2 dari 2 server tersedia.",
  "data": {
    "defaultServer": 1,
    "servers": [
      {
        "id": 1,
        "name": "Server 1",
        "default": true,
        "available": true
      },
      {
        "id": 2,
        "name": "Server 2",
        "default": false,
        "available": true
      }
    ],
    "note": "Pilih server lewat field \"server\" (1 atau 2) pada POST /v1/tts. Kedua server menghasilkan suara yang sama; pindah server bila salah satunya lambat atau gagal."
  }
}
```

## Gmail

Gmail Gateway (via akun Google yang terhubung)

### POST /toolkits/gmail/send

**Kirim email lewat akun Gmail yang terhubung**

- URL: `POST https://api.apipedia.id/v1/toolkits/gmail/send`
- Auth: `Authorization: Bearer <token>`
- operationId: `sendGmail`

Mengirim email lewat akun Google yang sudah dihubungkan di halaman Koneksi (toolkit `gmail`), dieksekusi lewat tool Composio `GMAIL_SEND_EMAIL`. Field `result` pada data berisi jawaban Gmail apa adanya.
Field body: `to` (wajib, alias `recipient_email`), `subject` (wajib), `body` (wajib kecuali `html` diisi; alias `text`), `html` (isi HTML, otomatis `is_html: true`), `is_html` (boolean, opsional), `cc` dan `bcc` (array alamat email).
Lampiran lewat `attachments` (array) atau `attachment` (satu objek): `filename` wajib, `mimetype` opsional (default application/octet-stream), `content` base64 (data URL juga diterima). Gmail hanya menerima SATU lampiran per pengiriman, maksimal 20 MB.
Error: 400 field wajib kosong/alamat tidak valid/lampiran lebih dari satu, 409 akun Gmail belum dihubungkan, 503 COMPOSIO_API_KEY server kosong, 502 Gmail menolak. Efek samping nyata: email benar-benar terkirim.

Body JSON:

```json
{
  "to": "pelanggan@contoh.com",
  "subject": "Invoice #INV-2026-0913",
  "body": "Halo Budi, total tagihan kamu Rp 25.000.",
  "cc": [
    "keuangan@contoh.com"
  ],
  "attachments": [
    {
      "filename": "invoice.pdf",
      "mimetype": "application/pdf",
      "content": "JVBERi0xLjQKJ..."
    }
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/gmail/send" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":"pelanggan@contoh.com","subject":"Invoice #INV-2026-0913","body":"Halo Budi, total tagihan kamu Rp 25.000.","cc":["keuangan@contoh.com"],"attachments":[{"filename":"invoice.pdf","mimetype":"application/pdf","content":"JVBERi0xLjQKJ..."}]}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Email berhasil dikirim lewat akun Gmail yang terhubung",
  "data": {
    "tool": "GMAIL_SEND_EMAIL",
    "result": {
      "id": "1925f3c2a7b4e8d1",
      "threadId": "1925f3c2a7b4e8d1",
      "labelIds": [
        "SENT"
      ]
    }
  }
}
```

### GET /toolkits/gmail/messages

**Ambil daftar email dari akun Gmail yang terhubung**

- URL: `GET https://api.apipedia.id/v1/toolkits/gmail/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `listGmailMessages`

Dieksekusi lewat tool Composio `GMAIL_FETCH_EMAILS`. Memerlukan koneksi aktif di halaman Koneksi; kalau belum terhubung, endpoint membalas HTTP 409 berisi permintaan untuk menghubungkan akun lebih dulu.
Hanya METADATA yang dipulangkan (subjek, pengirim, penerima, waktu, label) supaya respons tetap kecil; isi email diambil lewat `GET /toolkits/gmail/messages/{id}`. Paginasi memakai `page_token` dari `nextPageToken` respons sebelumnya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `limit` | query | integer | tidak | Jumlah email per halaman, 1-50. Nilai di luar rentang jatuh ke 10. default: 10; contoh: 10 |
| `q` | query | string | tidak | Kueri pencarian sintaks Gmail, mis. is:unread from:bank. contoh: is:unread |
| `page_token` | query | string | tidak | Kursor halaman berikutnya, diambil dari `nextPageToken` respons sebelumnya. Kosongkan untuk halaman pertama. Gmail memakai kursor, bukan nomor halaman. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/gmail/messages?limit=10&q=is%3Aunread" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar email berhasil diambil",
  "data": {
    "tool": "GMAIL_FETCH_EMAILS",
    "result": {
      "messages": [
        {
          "messageId": "1925f3c2a7b4e8d1",
          "threadId": "1925f3c2a7b4e8d1",
          "subject": "Tagihan September",
          "sender": "billing@contoh.com",
          "messageTimestamp": "2026-09-30T02:11:00Z"
        }
      ],
      "nextPageToken": null
    }
  }
}
```

### GET /toolkits/gmail/messages/{id}

**Ambil isi satu email Gmail**

- URL: `GET https://api.apipedia.id/v1/toolkits/gmail/messages/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getGmailMessage`

Dieksekusi lewat tool Composio `GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID`. Dipakai setelah `GET /toolkits/gmail/messages`, yang sengaja hanya memulangkan metadata supaya responsnya tetap kecil.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | `messageId` Gmail dari daftar email. contoh: 1925f3c2a7b4e8d1 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/gmail/messages/1925f3c2a7b4e8d1" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Isi email berhasil diambil",
  "data": {
    "tool": "GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID",
    "result": {
      "messageId": "1925f3c2a7b4e8d1",
      "threadId": "1925f3c2a7b4e8d1",
      "subject": "Tagihan September",
      "sender": "billing@contoh.com",
      "messageText": "Tagihan Anda untuk periode September sudah tersedia.",
      "messageTimestamp": "2026-09-30T02:11:00Z"
    }
  }
}
```

## Email

Email IMAP/SMTP: baca, cari, tandai, hapus, dan kirim/balas email dari banyak mailbox (Gmail, Outlook, Yahoo, Zoho, domain sendiri) plus inbox webhook, lewat satu token. Mailbox dihubungkan sekali di halaman Koneksi atau lewat POST /mail/accounts.

### GET /mail/detect

**Tebak server IMAP/SMTP dari alamat email**

- URL: `GET https://api.apipedia.id/v1/mail/detect`
- Auth: `Authorization: Bearer <token>`
- operationId: `detectMailSettings`

Menebak pengaturan IMAP/SMTP dari domain alamat email, tanpa login dan tanpa menyimpan apa pun. Urutannya: domain populer dikenali langsung (gmail.com, outlook.com/hotmail.com/live.com, icloud.com, yahoo.com/yahoo.co.id, zoho.com, fastmail.com); domain lain dicek lewat DNS MX (Google Workspace, Microsoft 365, iCloud, Yahoo, Zoho, Fastmail, Coremail, Tencent Exmail, Alibaba Mail, GoDaddy, Titan, Hostinger); bila MX ada tetapi tidak dikenali, ditebak `mail.<domain>` port 993/465 TLS dengan `guessed: true`. `settings` null berarti isi host secara manual di POST /mail/accounts. `presets` selalu berisi enam preset yang bisa dipakai lewat field `preset`.
Error: 400 parameter email kosong/tanpa @; 503 kunci enkripsi server belum diatur.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `email` | query | string | ya | Alamat email yang akan dihubungkan. contoh: budi@tokosejahtera.co.id |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/detect?email=budi%40tokosejahtera.co.id" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pengaturan server ditemukan",
  "data": {
    "domain": "tokosejahtera.co.id",
    "mx": [
      "aspmx.l.google.com",
      "alt1.aspmx.l.google.com",
      "alt2.aspmx.l.google.com"
    ],
    "settings": {
      "label": "Gmail / Google Workspace",
      "imap_host": "imap.gmail.com",
      "imap_port": 993,
      "imap_security": "tls",
      "smtp_host": "smtp.gmail.com",
      "smtp_port": 465,
      "smtp_security": "tls",
      "note": "Butuh Verifikasi 2 Langkah + App Password (myaccount.google.com/apppasswords). Password biasa tidak akan bisa.",
      "preset": "gmail"
    },
    "presets": [
      {
        "key": "gmail",
        "label": "Gmail / Google Workspace",
        "imap_host": "imap.gmail.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.gmail.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Butuh Verifikasi 2 Langkah + App Password (myaccount.google.com/apppasswords). Password biasa tidak akan bisa."
      },
      {
        "key": "outlook",
        "label": "Outlook / Microsoft 365",
        "imap_host": "outlook.office365.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.office365.com",
        "smtp_port": 587,
        "smtp_security": "starttls",
        "note": "Basic auth harus aktif untuk mailbox ini; banyak tenant Microsoft 365 hanya mengizinkan OAuth."
      },
      {
        "key": "icloud",
        "label": "iCloud Mail",
        "imap_host": "imap.mail.me.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.mail.me.com",
        "smtp_port": 587,
        "smtp_security": "starttls",
        "note": "Pakai app-specific password."
      },
      {
        "key": "yahoo",
        "label": "Yahoo Mail",
        "imap_host": "imap.mail.yahoo.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.mail.yahoo.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Pakai app password."
      },
      {
        "key": "zoho",
        "label": "Zoho Mail",
        "imap_host": "imap.zoho.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.zoho.com",
        "smtp_port": 465,
        "smtp_security": "tls"
      },
      {
        "key": "fastmail",
        "label": "Fastmail",
        "imap_host": "imap.fastmail.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.fastmail.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Pakai app password."
      }
    ]
  }
}
```

### GET /mail/accounts

**Daftar mailbox terhubung**

- URL: `GET https://api.apipedia.id/v1/mail/accounts`
- Auth: `Authorization: Bearer <token>`
- operationId: `listMailAccounts`

Semua mailbox aktif milik akun Anda: mailbox IMAP/SMTP (`toolkit: "email"`) dan inbox webhook (`toolkit: "email-webhook"`). Urut default dulu, lalu yang paling lama terhubung. Password tidak pernah dikembalikan; `meta` hanya berisi data publik (alamat, host). Pakai `id` sebagai `connection_id` di endpoint lain. Daftar kosong berarti belum ada mailbox - hubungkan lewat POST /mail/accounts atau halaman Koneksi.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/accounts" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar mailbox berhasil diambil",
  "data": {
    "items": [
      {
        "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
        "toolkit": "email",
        "accountLabel": "budi@tokosejahtera.co.id",
        "isDefault": true,
        "connectedAt": "2026-11-10T02:14:09.512Z",
        "meta": {
          "email": "budi@tokosejahtera.co.id",
          "imap_host": "imap.gmail.com",
          "smtp_host": "smtp.gmail.com",
          "from_name": "Budi - Toko Sejahtera"
        },
        "lastUsedAt": "2026-11-12T09:02:44.118Z"
      },
      {
        "id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
        "toolkit": "email-webhook",
        "accountLabel": "Pesanan toko online",
        "isDefault": false,
        "connectedAt": "2026-11-11T05:30:00.000Z",
        "lastUsedAt": null,
        "meta": {
          "kind": "webhook"
        }
      }
    ]
  }
}
```

### POST /mail/accounts

**Hubungkan mailbox IMAP/SMTP**

- URL: `POST https://api.apipedia.id/v1/mail/accounts`
- Auth: `Authorization: Bearer <token>`
- operationId: `connectMailAccount`

Menghubungkan mailbox baru. Server MENGUJI login IMAP (status INBOX) dan SMTP (verify) lebih dulu; kredensial baru disimpan (terenkripsi AES-256-GCM) bila keduanya berhasil. Mailbox pertama otomatis menjadi default. Akun yang sama juga bisa dihubungkan dari halaman Koneksi console.
Host: isi `preset` (gmail, outlook, icloud, yahoo, zoho, fastmail), ATAU isi `imap_host` + `smtp_host` manual, ATAU kosongkan keduanya supaya server menebak dari domain (sama dengan GET /mail/detect). Port IMAP yang diizinkan 993 atau 143, SMTP 465, 587, 25, atau 2525. Keamanan `tls` atau `starttls` (`none` ditolak). Host yang mengarah ke jaringan privat/localhost ditolak (anti-SSRF). `username` bawaan = `email`. Gmail, Yahoo, iCloud, dan Fastmail butuh App Password, bukan password biasa.
Error: 400 email/password kosong, host tidak bisa ditebak, port/keamanan tidak diizinkan, host privat; 401 login IMAP/SMTP ditolak; 502 server email tidak bisa dihubungi (pesan diawali "IMAP berhasil, tetapi SMTP gagal." bila hanya SMTP yang gagal).

Body JSON:

```json
{
  "email": "budi@tokosejahtera.co.id",
  "password": "abcd efgh ijkl mnop",
  "label": "Email toko",
  "from_name": "Budi - Toko Sejahtera",
  "preset": "gmail"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/accounts" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"budi@tokosejahtera.co.id","password":"abcd efgh ijkl mnop","label":"Email toko","from_name":"Budi - Toko Sejahtera","preset":"gmail"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Mailbox budi@tokosejahtera.co.id berhasil dihubungkan",
  "data": {
    "connection": {
      "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
      "toolkit": "email",
      "accountLabel": "Email toko",
      "isDefault": true,
      "connectedAt": "2026-11-10T02:14:09.512Z",
      "meta": {
        "email": "budi@tokosejahtera.co.id",
        "imap_host": "imap.gmail.com",
        "smtp_host": "smtp.gmail.com",
        "from_name": "Budi - Toko Sejahtera"
      }
    }
  }
}
```

### POST /mail/accounts/{id}/test

**Uji ulang login IMAP dan SMTP**

- URL: `POST https://api.apipedia.id/v1/mail/accounts/{id}/test`
- Auth: `Authorization: Bearer <token>`
- operationId: `testMailAccount`

Menguji ulang login IMAP dan SMTP sebuah mailbox yang sudah terhubung memakai kredensial tersimpan - berguna setelah password atau App Password diganti. Tidak mengubah apa pun. Inbox webhook ditolak (400) karena tidak punya login.
Error: 400 id bukan uuid / akun webhook; 401 login ditolak; 404 akun tidak ditemukan; 502 server email tidak bisa dihubungi.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id koneksi mailbox (uuid) dari GET /mail/accounts. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/accounts/3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d/test" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Login IMAP dan SMTP berhasil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
  }
}
```

### GET /mail/folders

**Daftar folder mailbox**

- URL: `GET https://api.apipedia.id/v1/mail/folders`
- Auth: `Authorization: Bearer <token>`
- operationId: `listMailFolders`

Daftar folder IMAP yang bisa dipilih (folder \Noselect disaring) beserta jumlah email dan yang belum dibaca. `special_use` menandai folder khusus (\Inbox, \Sent, \Trash, \Drafts, \Junk, \All, \Flagged) sehingga Anda tidak perlu menebak nama lokal seperti "[Gmail]/Sent Mail". Inbox webhook selalu mengembalikan satu folder `INBOX`.
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Prasyarat: mailbox harus terhubung lebih dulu - di halaman Koneksi console (console.apipedia.id/connections, kartu "Email IMAP/SMTP" atau "Inbox webhook") atau lewat POST /mail/accounts dan POST /mail/webhooks. Password disimpan terenkripsi AES-256-GCM dan tidak pernah dikembalikan; setiap permintaan langsung bicara ke server IMAP/SMTP Anda (isi email tidak di-cache di APIPedia).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/folders?connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar folder berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "items": [
      {
        "path": "INBOX",
        "name": "INBOX",
        "special_use": "\\Inbox",
        "messages": 1824,
        "unseen": 12
      },
      {
        "path": "[Gmail]/Sent Mail",
        "name": "Sent Mail",
        "special_use": "\\Sent",
        "messages": 640,
        "unseen": 0
      },
      {
        "path": "[Gmail]/Drafts",
        "name": "Drafts",
        "special_use": "\\Drafts",
        "messages": 3,
        "unseen": 0
      },
      {
        "path": "[Gmail]/Trash",
        "name": "Trash",
        "special_use": "\\Trash",
        "messages": 57,
        "unseen": 2
      },
      {
        "path": "Pesanan",
        "name": "Pesanan",
        "special_use": null,
        "messages": 311,
        "unseen": 4
      }
    ]
  }
}
```

### GET /mail/messages

**Daftar dan cari email**

- URL: `GET https://api.apipedia.id/v1/mail/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `listMailMessages`

Daftar email di satu folder, terbaru dulu, per halaman. Tanpa filter, halaman dihitung dari nomor urut (cepat untuk mailbox besar) dan `total` = jumlah email di folder. Dengan filter (`query`, `unread_only`, `flagged_only`, `since`), server menjalankan IMAP SEARCH: `query` dicocokkan ke subjek, pengirim, penerima, ATAU isi; `total` = jumlah yang cocok. Isi email tidak ikut - baca dengan GET /mail/messages/{uid}. Membaca daftar TIDAK menandai email sebagai dibaca.
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 since bukan tanggal ISO 8601; 401 login IMAP ditolak; 502 server email tidak bisa dihubungi.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. default: INBOX; contoh: INBOX |
| `limit` | query | integer | tidak | Email per halaman, 1-100. default: 25; contoh: 25 |
| `page` | query | integer | tidak | Nomor halaman, mulai 1. Lanjutkan selama `has_more` true. default: 1 |
| `query` | query | string | tidak | Kata kunci (maks 200 karakter) di subjek, pengirim, penerima, atau isi. Alias: `q`. contoh: pesanan |
| `unread_only` | query | boolean | tidak | `true` atau `1`: hanya email yang belum dibaca. default: false |
| `flagged_only` | query | boolean | tidak | `true` atau `1`: hanya email berbintang. default: false |
| `since` | query | string | tidak | Hanya email sejak tanggal ini (ISO 8601), mis. `2026-11-01`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages?folder=INBOX&limit=25&query=pesanan&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar email berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "total": 37,
    "page": 1,
    "limit": 25,
    "has_more": true,
    "items": [
      {
        "uid": 48213,
        "subject": "Konfirmasi pesanan #INV-2026-1187",
        "from": {
          "name": "Rina Kartika",
          "address": "rina.kartika@contoh.co.id"
        },
        "to": [
          {
            "name": "Budi - Toko Sejahtera",
            "address": "budi@tokosejahtera.co.id"
          }
        ],
        "date": "2026-11-12T08:41:27.000Z",
        "message_id": "<CAF3x9qL0p2@mail.contoh.co.id>",
        "seen": false,
        "flagged": false,
        "answered": false,
        "size": 18342,
        "has_attachments": true
      },
      {
        "uid": 48207,
        "subject": "Re: Penawaran harga grosir November",
        "from": {
          "name": "Dedi Pratama",
          "address": "dedi@supplierjaya.id"
        },
        "to": [
          {
            "name": null,
            "address": "budi@tokosejahtera.co.id"
          }
        ],
        "date": "2026-11-11T14:05:10.000Z",
        "message_id": "<20261111140510.4f2a@supplierjaya.id>",
        "seen": true,
        "flagged": true,
        "answered": true,
        "size": 7421,
        "has_attachments": false
      }
    ]
  }
}
```

### GET /mail/messages/{uid}

**Baca satu email**

- URL: `GET https://api.apipedia.id/v1/mail/messages/{uid}`
- Auth: `Authorization: Bearer <token>`
- operationId: `readMailMessage`

Mengambil satu email lengkap: header, isi `text` dan `html` (hasil parse MIME), penerima cc/reply-to, header threading (`in_reply_to`, `references`), dan daftar lampiran (`index` dipakai untuk mengunduh lewat /mail/messages/{uid}/attachments/{index}). Bawaannya email ditandai sudah dibaca (flag \Seen); kirim `mark_read=false` untuk membaca tanpa menandai.
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 uid tidak valid; 404 uid tidak ada di folder itu.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. contoh: 48213 |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. default: INBOX; contoh: INBOX |
| `mark_read` | query | boolean | tidak | `false` supaya email TIDAK ditandai dibaca. default: true |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Email berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "message": {
      "uid": 48213,
      "subject": "Konfirmasi pesanan #INV-2026-1187",
      "from": {
        "name": "Rina Kartika",
        "address": "rina.kartika@contoh.co.id"
      },
      "to": [
        {
          "name": "Budi - Toko Sejahtera",
          "address": "budi@tokosejahtera.co.id"
        }
      ],
      "date": "2026-11-12T08:41:27.000Z",
      "message_id": "<CAF3x9qL0p2@mail.contoh.co.id>",
      "seen": true,
      "flagged": false,
      "answered": false,
      "size": 18342,
      "has_attachments": true,
      "cc": [
        {
          "name": "Admin Gudang",
          "address": "gudang@tokosejahtera.co.id"
        }
      ],
      "reply_to": [],
      "in_reply_to": null,
      "references": null,
      "text": "Halo Pak Budi,\n\nPesanan #INV-2026-1187 sudah kami terima dan akan dikirim besok pagi. Faktur terlampir.\n\nSalam,\nRina",
      "html": "<p>Halo Pak Budi,</p><p>Pesanan <b>#INV-2026-1187</b> sudah kami terima dan akan dikirim besok pagi. Faktur terlampir.</p><p>Salam,<br>Rina</p>",
      "attachments": [
        {
          "index": 0,
          "filename": "faktur-INV-2026-1187.pdf",
          "content_type": "application/pdf",
          "size": 15210,
          "inline": false
        }
      ]
    }
  }
}
```

### PATCH /mail/messages/{uid}

**Tandai dibaca/belum dibaca atau berbintang**

- URL: `PATCH https://api.apipedia.id/v1/mail/messages/{uid}`
- Auth: `Authorization: Bearer <token>`
- operationId: `updateMailMessageFlags`

Mengubah flag IMAP sebuah email: `seen` (\Seen, dibaca) dan/atau `flagged` (\Flagged, berbintang). Isi minimal salah satu, bertipe boolean. `folder` dan `connection_id` boleh di query atau body. Jawaban menggemakan nilai yang dikirim (field yang tidak dikirim tidak muncul).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 uid tidak valid atau tidak ada seen/flagged boolean; 404 uid tidak ditemukan (inbox webhook).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. contoh: 48213 |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. default: INBOX; contoh: INBOX |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Body JSON:

```json
{
  "seen": true,
  "flagged": true
}
```

Contoh request:

```bash
curl -X PATCH "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"seen":true,"flagged":true}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status email diperbarui",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "uid": 48213,
    "seen": true,
    "flagged": true
  }
}
```

### DELETE /mail/messages/{uid}

**Pindahkan email ke Sampah**

- URL: `DELETE https://api.apipedia.id/v1/mail/messages/{uid}`
- Auth: `Authorization: Bearer <token>`
- operationId: `deleteMailMessage`

Memindahkan email ke folder Sampah mailbox (folder ber-special-use \Trash) dan mengembalikan namanya di `moved_to`. Email dihapus permanen (`moved_to: null`) bila `permanent=true`, bila server tidak punya folder Sampah, atau bila email sudah berada di folder Sampah. Email inbox webhook selalu dihapus permanen.
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 uid tidak valid; 404 uid tidak ditemukan (inbox webhook).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. contoh: 48213 |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. default: INBOX; contoh: INBOX |
| `permanent` | query | boolean | tidak | `true` untuk menghapus permanen tanpa lewat Sampah. Tidak bisa dibatalkan. default: false |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Email dipindahkan ke [Gmail]/Trash",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "uid": 48213,
    "moved_to": "[Gmail]/Trash"
  }
}
```

### GET /mail/messages/{uid}/attachments/{index}

**Unduh lampiran email**

- URL: `GET https://api.apipedia.id/v1/mail/messages/{uid}/attachments/{index}`
- Auth: `Authorization: Bearer <token>`
- operationId: `downloadMailAttachment`

Mengunduh satu lampiran sebagai berkas mentah dengan `Content-Type` asli lampiran dan `Content-Disposition: attachment; filename=...`. `index` diambil dari `message.attachments[].index` di GET /mail/messages/{uid}. Jawaban sukses BUKAN JSON; error tetap amplop JSON. Inbox webhook tidak menyimpan lampiran (selalu 404).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 uid tidak valid; 404 email atau lampiran tidak ditemukan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. contoh: 48213 |
| `index` | path | integer | ya | Urutan lampiran, mulai 0. contoh: 0 |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. default: INBOX; contoh: INBOX |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages/48213/attachments/0?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berhasil",
  "data": null
}
```

### POST /mail/send

**Kirim atau balas email**

- URL: `POST https://api.apipedia.id/v1/mail/send`
- Auth: `Authorization: Bearer <token>`
- operationId: `sendMail`

Mengirim email lewat SMTP mailbox yang dipilih, dari alamat mailbox itu (nama pengirim = `from_name` saat dihubungkan). Salinan disimpan ke folder Terkirim (\Sent) lewat IMAP - Gmail melakukannya sendiri; `saved_to_sent` false berarti salinan gagal disimpan, email TETAP terkirim.
Field:
- `to` (wajib kecuali membalas), `cc`, `bcc`: string dipisah koma atau array, maks 50 alamat per field.
- `subject` (maks 998 karakter): wajib kecuali membalas - saat membalas bawaannya "Re: <subjek asli>".
- `text` dan/atau `html`: salah satu wajib, total maks 2 MB.
- `attachments`: maks 10, tiap item `{ filename, content_base64, content_type? }`. Seluruh body JSON maks 12 MB.
- `reply_to_uid` + `folder` (bawaan INBOX): membalas email itu - header In-Reply-To/References diisi, `to` bawaan = Reply-To/pengirim asli, dan email asli ditandai \Answered.
Endpoint ini punya efek samping nyata (email terkirim atas nama pemilik mailbox). Inbox webhook tidak bisa mengirim (400).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 field tidak valid; 404 email yang dibalas tidak ditemukan; 413 isi lebih dari 2 MB; 401/502 server SMTP menolak atau tidak bisa dihubungi.

Body JSON:

```json
{
  "to": [
    "rina.kartika@contoh.co.id"
  ],
  "cc": "gudang@tokosejahtera.co.id",
  "subject": "Faktur pesanan #INV-2026-1187",
  "text": "Halo Bu Rina,\n\nTerlampir faktur pesanan #INV-2026-1187. Terima kasih.\n\nSalam,\nBudi",
  "html": "<p>Halo Bu Rina,</p><p>Terlampir faktur pesanan <b>#INV-2026-1187</b>. Terima kasih.</p><p>Salam,<br>Budi</p>",
  "attachments": [
    {
      "filename": "faktur.txt",
      "content_base64": "RmFrdHVyICNJTlYtMjAyNi0xMTg3",
      "content_type": "text/plain"
    }
  ],
  "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/send" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":["rina.kartika@contoh.co.id"],"cc":"gudang@tokosejahtera.co.id","subject":"Faktur pesanan #INV-2026-1187","text":"Halo Bu Rina,\n\nTerlampir faktur pesanan #INV-2026-1187. Terima kasih.\n\nSalam,\nBudi","html":"<p>Halo Bu Rina,</p><p>Terlampir faktur pesanan <b>#INV-2026-1187</b>. Terima kasih.</p><p>Salam,<br>Budi</p>","attachments":[{"filename":"faktur.txt","content_base64":"RmFrdHVyICNJTlYtMjAyNi0xMTg3","content_type":"text/plain"}],"connection_id":"3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Email berhasil dikirim",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "message_id": "<7c1e5f0a-3b2d-4e8f-9a61-0d2c4b6e8f10@tokosejahtera.co.id>",
    "accepted": [
      "rina.kartika@contoh.co.id",
      "gudang@tokosejahtera.co.id"
    ],
    "rejected": [],
    "saved_to_sent": true
  }
}
```

### POST /mail/webhooks

**Buat inbox webhook (URL rahasia)**

- URL: `POST https://api.apipedia.id/v1/mail/webhooks`
- Auth: `Authorization: Bearer <token>`
- operationId: `createMailWebhookInbox`

Membuat kotak masuk berjenis webhook (toolkit `email-webhook`) dan mengembalikan `webhook_url` rahasia SEKALI saja - simpan sekarang; yang disimpan APIPedia hanya hash SHA-256-nya. Sistem lain (toko online, form, monitoring) cukup POST email JSON ke URL itu; email muncul di halaman Email console dan di GET /mail/messages seperti mailbox lain (pilih dengan `connection_id` inbox ini). Inbox webhook hanya MENERIMA: kirim/balas dari akun ini ditolak.
Mengirim ke `webhook_url` (publik, TANPA token, `Content-Type: application/json`, body maks 600 KB, maks 60 email/menit per inbox):
- `from` (wajib, alamat email), `from_name` (≤200 karakter)
- `to` (wajib, array 1-50 alamat), `cc` (array ≤50)
- `subject` (wajib, satu baris, ≤998 karakter)
- `text` (≤200 KB) dan/atau `html` (≤400 KB): salah satu wajib
- `date` (ISO 8601, ≤24 jam ke depan; bawaan sekarang), `message_id` (tanpa spasi, ≤300; dipakai untuk dedup)
Field lain ditolak. Jawaban: 201 `{ uid, duplicate: false }` ("Email diterima"), 200 `{ uid: null, duplicate: true }` bila message_id sudah ada, 400 bukan JSON valid, 404 URL salah/inbox diputus, 413 lebih dari 600 KB, 415 Content-Type bukan JSON, 422 validasi (`data.errors` berisi daftar kesalahan), 429 lebih dari 60 email/menit.
Contoh: `curl -X POST "<webhook_url>" -H "Content-Type: application/json" -d '{"from":"noreply@tokoonline.id","to":["budi@tokosejahtera.co.id"],"subject":"Pesanan baru #1187","text":"Ada pesanan baru."}'`

Body JSON (opsional):

```json
{
  "label": "Pesanan toko online"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/webhooks" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"Pesanan toko online"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Inbox webhook dibuat. Simpan URL-nya sekarang: tidak akan ditampilkan lagi.",
  "data": {
    "connection": {
      "id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
      "toolkit": "email-webhook",
      "accountLabel": "Pesanan toko online",
      "isDefault": true,
      "connectedAt": "2026-11-10T02:14:09.512Z",
      "meta": {
        "kind": "webhook"
      }
    },
    "webhook_url": "https://api.apipedia.id/v1/mail-hook/q8ZxV3nK1pT7rWm2cYb5hLd0sFj4gHe9"
  }
}
```

### POST /mail/webhooks/{id}/rotate

**Ganti URL inbox webhook**

- URL: `POST https://api.apipedia.id/v1/mail/webhooks/{id}/rotate`
- Auth: `Authorization: Bearer <token>`
- operationId: `rotateMailWebhookUrl`

Membuat URL rahasia baru untuk inbox webhook; URL lama LANGSUNG tidak berlaku (404). Email yang sudah diterima tidak terpengaruh. URL baru hanya tampil di jawaban ini.
Error: 400 id bukan uuid; 404 inbox webhook tidak ditemukan (id milik mailbox IMAP juga 404).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id koneksi inbox webhook (uuid) dari GET /mail/accounts. contoh: 8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57 |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/webhooks/8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57/rotate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "URL webhook diganti. URL lama tidak berlaku lagi.",
  "data": {
    "connection_id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
    "webhook_url": "https://api.apipedia.id/v1/mail-hook/Lm4Rt8Wq2Zx6Cv0Bn3Kj7Hg1Fd5Sa9Pe"
  }
}
```

### POST /mail-hook/{secret}

**Terima email ke inbox webhook (publik)**

- URL: `POST https://api.apipedia.id/v1/mail-hook/{secret}`
- **INTERNAL** - dipanggil pihak ketiga, bukan oleh agent
- Auth: tidak perlu token
- operationId: `receiveMailWebhook`

URL rahasia hasil POST /mail/webhooks. PUBLIK, tanpa token: rahasia di path itulah autentikasinya. Wajib `Content-Type: application/json`, body maks 600 KB, maks 60 email per menit per inbox. Field yang dikenal: from, from_name, to, cc, subject, text, html, date, message_id (lainnya ditolak 422). `message_id` yang sama dibuang sebagai duplikat (200, `duplicate: true`).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `secret` | path | string | ya | Bagian rahasia URL webhook (20-64 karakter base64url). contoh: q8ZxV3nK1pT7rWm2cYb5hLd0sFj4gHe9 |

Body JSON:

```json
{
  "from": "noreply@tokoonline.id",
  "from_name": "Toko Online",
  "to": [
    "budi@tokosejahtera.co.id"
  ],
  "subject": "Pesanan baru #1187",
  "text": "Ada pesanan baru dari Rina Kartika senilai Rp 450.000.",
  "message_id": "order-1187@tokoonline.id"
}
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Email diterima",
  "data": {
    "uid": 912,
    "duplicate": false
  }
}
```

## Cloud Drive

Cloud Drive: jelajahi, unggah, unduh, buat folder, ganti nama, hapus, cek kuota, dan cari berkas di Dropbox, OneDrive, Yandex Disk, MEGA, pCloud, S3/R2/Wasabi/B2/MinIO, dan Telegram dengan path yang sama untuk semua penyedia. Akun penyimpanan dihubungkan sekali di halaman Koneksi atau lewat API.

### GET /drive/providers

**Daftar penyedia dan field kredensial**

- URL: `GET https://api.apipedia.id/v1/drive/providers`
- Auth: `Authorization: Bearer <token>`
- operationId: `listDriveProviders`

Tujuh penyedia yang didukung beserta field kredensial untuk POST /drive/accounts. `oauth: true` (Dropbox, OneDrive, Yandex Disk) berarti dihubungkan lewat login di browser (POST /drive/oauth/{provider}/start), bukan form. `provider` adalah nilai yang dikirim ke field `provider`; `toolkit` adalah nama koneksi di GET /connections. Google Drive tidak termasuk di sini - pakai produk Google Drive (koneksi OAuth `googledrive`).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/providers" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar penyedia drive",
  "data": {
    "items": [
      {
        "toolkit": "drive-s3",
        "provider": "s3",
        "name": "S3 / kompatibel S3",
        "description": "AWS S3, Cloudflare R2, Wasabi, Backblaze B2, MinIO, Supabase Storage (S3), IDCloudHost, dll.",
        "fields": [
          {
            "key": "access_key_id",
            "label": "Access key ID",
            "type": "text",
            "required": true
          },
          {
            "key": "secret_access_key",
            "label": "Secret access key",
            "type": "password",
            "required": true
          },
          {
            "key": "bucket",
            "label": "Bucket",
            "type": "text",
            "required": true
          },
          {
            "key": "region",
            "label": "Region",
            "type": "text",
            "placeholder": "auto / us-east-1 / ap-southeast-1"
          },
          {
            "key": "endpoint",
            "label": "Endpoint (kosongkan untuk AWS)",
            "type": "url",
            "placeholder": "https://<akun>.r2.cloudflarestorage.com"
          },
          {
            "key": "prefix",
            "label": "Folder dasar (opsional)",
            "type": "text",
            "placeholder": "mis. backup/"
          },
          {
            "key": "force_path_style",
            "label": "Path-style URL (MinIO/Supabase)",
            "type": "boolean"
          }
        ],
        "oauth": false
      },
      {
        "toolkit": "drive-mega",
        "provider": "mega",
        "name": "MEGA",
        "description": "Penyimpanan terenkripsi end-to-end, 20 GB gratis.",
        "fields": [
          {
            "key": "email",
            "label": "Email MEGA",
            "type": "email",
            "required": true
          },
          {
            "key": "password",
            "label": "Password MEGA",
            "type": "password",
            "required": true
          }
        ],
        "oauth": false
      },
      {
        "toolkit": "drive-pcloud",
        "provider": "pcloud",
        "name": "pCloud",
        "description": "10 GB gratis, server di AS atau Eropa.",
        "fields": [
          {
            "key": "username",
            "label": "Email pCloud",
            "type": "email",
            "required": true
          },
          {
            "key": "password",
            "label": "Password pCloud",
            "type": "password",
            "required": true
          }
        ],
        "oauth": false
      },
      {
        "toolkit": "drive-dropbox",
        "provider": "dropbox",
        "name": "Dropbox",
        "description": "Login lewat akun Dropbox (OAuth). 2 GB gratis.",
        "fields": [],
        "oauth": true
      },
      {
        "toolkit": "drive-onedrive",
        "provider": "onedrive",
        "name": "OneDrive",
        "description": "Login lewat akun Microsoft (OAuth). 5 GB gratis.",
        "fields": [],
        "oauth": true
      },
      {
        "toolkit": "drive-yandex",
        "provider": "yandex",
        "name": "Yandex Disk",
        "description": "Login lewat akun Yandex (OAuth). 10 GB gratis.",
        "fields": [],
        "oauth": true
      },
      {
        "toolkit": "drive-telegram",
        "provider": "telegram",
        "name": "Telegram",
        "description": "Bot Telegram menyimpan berkas di chat/channel pribadimu. Maks 50 MB unggah, 20 MB unduh per berkas.",
        "fields": [
          {
            "key": "bot_token",
            "label": "Token bot (dari @BotFather)",
            "type": "password",
            "required": true
          },
          {
            "key": "chat_id",
            "label": "Chat ID channel/grup penyimpanan",
            "type": "text",
            "required": true,
            "placeholder": "-100xxxxxxxxxx"
          }
        ],
        "oauth": false
      }
    ]
  }
}
```

### GET /drive/accounts

**Daftar akun drive terhubung**

- URL: `GET https://api.apipedia.id/v1/drive/accounts`
- Auth: `Authorization: Bearer <token>`
- operationId: `listDriveAccounts`

Semua akun penyimpanan aktif milik Anda dari ketujuh penyedia, default dulu. Kredensial tidak pernah dikembalikan; `meta` hanya data publik (bucket, host, email, region). Pakai `id` sebagai `connection_id` di endpoint lain.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/accounts" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar akun drive berhasil diambil",
  "data": {
    "items": [
      {
        "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
        "toolkit": "drive-s3",
        "accountLabel": "arsip-toko (abc123.r2.cloudflarestorage.com)",
        "isDefault": true,
        "connectedAt": "2026-11-10T02:14:09.512Z",
        "meta": {
          "provider": "s3",
          "bucket": "arsip-toko",
          "host": "abc123.r2.cloudflarestorage.com",
          "prefix": null
        },
        "lastUsedAt": "2026-11-12T09:02:44.118Z"
      },
      {
        "id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
        "toolkit": "drive-dropbox",
        "accountLabel": "budi@tokosejahtera.co.id",
        "isDefault": false,
        "connectedAt": "2026-11-11T05:30:00.000Z",
        "lastUsedAt": null,
        "meta": {
          "provider": "dropbox",
          "email": "budi@tokosejahtera.co.id",
          "name": "Budi Santoso"
        }
      }
    ]
  }
}
```

### POST /drive/accounts

**Hubungkan akun penyimpanan (form kredensial)**

- URL: `POST https://api.apipedia.id/v1/drive/accounts`
- Auth: `Authorization: Bearer <token>`
- operationId: `connectDriveAccount`

Menghubungkan akun S3/kompatibel S3, MEGA, pCloud, atau bot Telegram. `provider` wajib (`s3`, `mega`, `pcloud`, `telegram`; boleh juga bentuk toolkit `drive-s3`); field lain sesuai GET /drive/providers. Kredensial DIUJI lebih dulu (S3: HeadBucket; MEGA/pCloud: login; Telegram: getMe + getChat) dan baru disimpan terenkripsi bila berhasil. Akun pertama otomatis jadi default. `label` opsional (bawaan: `<bucket> (<host>)`, email, atau nama chat).
- S3: `access_key_id`, `secret_access_key`, `bucket` wajib; `region` (bawaan `auto`), `endpoint` (https, kosongkan untuk AWS; host privat ditolak), `prefix`, `force_path_style` (MinIO/Supabase).
- MEGA: `email`, `password`. pCloud: `username` (email), `password` (server AS/EU dipilih otomatis).
- Telegram: `bot_token` dari @BotFather dan `chat_id` (angka -100... atau @namachannel); bot harus anggota chat itu.
Dropbox, OneDrive, dan Yandex Disk ditolak di sini (400) - pakai POST /drive/oauth/{provider}/start.
Error: 400 provider tidak dikenal, field wajib kosong, endpoint bukan https/host privat, bucket tidak bisa diakses, token/chat Telegram tidak valid; 401 login MEGA/pCloud gagal.

Body JSON:

```json
{
  "provider": "s3",
  "access_key_id": "2f6c1b8e4a9d0c37e5b1f2a8d4c6e9b0",
  "secret_access_key": "8b2e4f6a1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f1a2b3c4d5e6f7a8b9c0d1e2f",
  "bucket": "arsip-toko",
  "region": "auto",
  "endpoint": "https://abc123.r2.cloudflarestorage.com"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/drive/accounts" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"s3","access_key_id":"2f6c1b8e4a9d0c37e5b1f2a8d4c6e9b0","secret_access_key":"8b2e4f6a1c3d5e7f9a0b2c4d6e8f0a1b3c5d7e9f1a2b3c4d5e6f7a8b9c0d1e2f","bucket":"arsip-toko","region":"auto","endpoint":"https://abc123.r2.cloudflarestorage.com"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "S3 / kompatibel S3 berhasil dihubungkan",
  "data": {
    "connection": {
      "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
      "toolkit": "drive-s3",
      "accountLabel": "arsip-toko (abc123.r2.cloudflarestorage.com)",
      "isDefault": true,
      "connectedAt": "2026-11-10T02:14:09.512Z",
      "meta": {
        "provider": "s3",
        "bucket": "arsip-toko",
        "host": "abc123.r2.cloudflarestorage.com",
        "prefix": null
      }
    }
  }
}
```

### POST /drive/oauth/{provider}/start

**Mulai login OAuth Dropbox/OneDrive/Yandex Disk**

- URL: `POST https://api.apipedia.id/v1/drive/oauth/{provider}/start`
- Auth: `Authorization: Bearer <token>`
- operationId: `startDriveOAuth`

Mengembalikan `redirectUrl` halaman izin penyedia. Buka di browser pengguna; setelah izin diberikan penyedia memanggil callback APIPedia, akun disimpan, lalu browser diarahkan ke halaman Koneksi console (`/connections?toolkit=drive-<provider>&status=active` atau `status=failed&reason=...`). Tautan berlaku 15 menit (state bertanda tangan HMAC). Cek hasilnya dengan GET /drive/accounts. Token akses disegarkan otomatis memakai refresh token.
Body opsional: `label` (nama akun di console; bawaan email/login penyedia) dan `app_origin` (origin console tujuan kembali; hanya console.apipedia.id atau localhost yang diterima).
Error: 400 provider tidak dikenal atau bukan penyedia OAuth; 503 login penyedia ini belum diaktifkan di server (client id/secret kosong) atau kunci enkripsi belum diatur.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `provider` | path | string | ya | Penyedia OAuth: `dropbox`, `onedrive`, atau `yandex`. pilihan: dropbox, onedrive, yandex; contoh: dropbox |

Body JSON (opsional):

```json
{
  "label": "Dropbox kantor"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/drive/oauth/dropbox/start" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"Dropbox kantor"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Buka redirectUrl untuk login",
  "data": {
    "redirectUrl": "https://www.dropbox.com/oauth2/authorize?client_id=k3x9q2m7v1p8w4z&response_type=code&redirect_uri=https%3A%2F%2Fapi.apipedia.id%2Fv1%2Fdrive-oauth%2Fdropbox%2Fcallback&state=eyJ1Ijoi...&token_access_type=offline"
  }
}
```

### GET /drive/files

**Isi folder**

- URL: `GET https://api.apipedia.id/v1/drive/files`
- Auth: `Authorization: Bearer <token>`
- operationId: `listDriveFiles`

Daftar berkas dan subfolder di satu folder, folder dulu lalu urut nama. Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter). Bawaan `path` adalah akar `/`. Setiap item: `name`, `path`, `is_folder`, `size` (byte; 0 untuk folder), `mime_type` (null untuk folder; ditebak dari ekstensi bila penyedia tidak memberi), `modified_at` (ISO 8601 atau null). Maks sekitar 5.000 item per folder.
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Prasyarat: akun penyimpanan harus terhubung lebih dulu - di halaman Koneksi console (console.apipedia.id/connections, kartu Cloud Drive per penyedia), lewat POST /drive/accounts (S3, MEGA, pCloud, Telegram), atau POST /drive/oauth/{provider}/start (Dropbox, OneDrive, Yandex Disk). Kredensial disimpan terenkripsi AES-256-GCM.
Error lain: 400 path mengandung ".." atau path berkas (MEGA); 404 path tidak ditemukan; 401/502 penyedia menolak/gagal.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `path` | query | string | tidak | Folder yang dibuka, mis. `/laporan/2026`. default: /; contoh: /laporan/2026 |
| `connection_id` | query | string | tidak | Id koneksi drive (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/files?path=%2Flaporan%2F2026&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Isi folder berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "provider": "s3",
    "path": "/laporan/2026",
    "items": [
      {
        "name": "foto-produk",
        "path": "/laporan/2026/foto-produk",
        "is_folder": true,
        "size": 0,
        "mime_type": null,
        "modified_at": null
      },
      {
        "name": "faktur-november.pdf",
        "path": "/laporan/2026/faktur-november.pdf",
        "is_folder": false,
        "size": 248113,
        "mime_type": "application/pdf",
        "modified_at": "2026-11-12T03:15:42.000Z"
      },
      {
        "name": "rekap-penjualan.xlsx",
        "path": "/laporan/2026/rekap-penjualan.xlsx",
        "is_folder": false,
        "size": 53120,
        "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "modified_at": "2026-11-10T07:22:05.000Z"
      }
    ]
  }
}
```

### DELETE /drive/files

**Hapus berkas atau folder**

- URL: `DELETE https://api.apipedia.id/v1/drive/files`
- Auth: `Authorization: Bearer <token>`
- operationId: `deleteDrivePath`

Menghapus berkas, atau folder beserta seluruh isinya. Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter). Folder akar `/` tidak bisa dihapus. Perilaku per penyedia: MEGA ke Rubbish Bin, OneDrive ke Recycle bin, Yandex Disk ke Trash, Dropbox mengikuti riwayat berkas Dropbox; S3, pCloud, dan Telegram menghapus permanen (Telegram juga menghapus pesannya). S3 menolak folder berisi lebih dari 10.000 objek (413).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 path kosong/akar; 404 path tidak ditemukan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `path` | query | string | ya | Berkas atau folder yang dihapus. contoh: /laporan/2026/faktur-november.pdf |
| `connection_id` | query | string | tidak | Id koneksi drive (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/drive/files?path=%2Flaporan%2F2026%2Ffaktur-november.pdf&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berhasil dihapus",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "path": "/laporan/2026/faktur-november.pdf"
  }
}
```

### GET /drive/download

**Unduh berkas**

- URL: `GET https://api.apipedia.id/v1/drive/download`
- Auth: `Authorization: Bearer <token>`
- operationId: `downloadDriveFile`

Mengunduh satu berkas. Bawaannya jawaban berupa berkas mentah (stream) dengan `Content-Type` berkas dan `Content-Disposition: attachment`. Dengan `format=base64` jawabannya amplop JSON berisi `content_base64` - untuk klien yang hanya bisa membaca JSON, maks 10 MB. Bot Telegram hanya bisa mengunduh berkas sampai 20 MB; folder (MEGA) tidak bisa diunduh langsung. Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 path kosong/folder; 404 berkas tidak ditemukan; 413 format=base64 untuk berkas >10 MB atau berkas Telegram >20 MB.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `path` | query | string | ya | Path berkas yang diunduh. contoh: /laporan/2026/faktur-november.pdf |
| `format` | query | string | tidak | `base64`: amplop JSON berisi isi berkas (maks 10 MB). Kosongkan untuk berkas mentah. pilihan: base64 |
| `connection_id` | query | string | tidak | Id koneksi drive (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/download?path=%2Flaporan%2F2026%2Ffaktur-november.pdf&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berkas berhasil diunduh",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "path": "/catatan/todo.txt",
    "name": "todo.txt",
    "size": 27,
    "mime_type": "text/plain",
    "content_base64": "LSBLaXJpbSBmYWt0dXIgSU5WLTExODcK"
  }
}
```

### POST /drive/upload

**Unggah berkas**

- URL: `POST https://api.apipedia.id/v1/drive/upload`
- Auth: `Authorization: Bearer <token>`
- operationId: `uploadDriveFile`

Mengunggah satu berkas ke folder `path` dengan nama `filename` (berkas bernama sama DITIMPA). Dua cara mengirim isi:
- Body mentah (disarankan): `Content-Type: application/octet-stream` (atau tipe MIME berkas), `path` dan `filename` di query, maks 50 MB. Contoh: `curl -X POST "https://api.apipedia.id/v1/drive/upload?path=/laporan/2026&filename=faktur.pdf" -H "Authorization: Bearer apk_..." -H "Content-Type: application/pdf" --data-binary @faktur.pdf`
- JSON `{ path, filename, content_base64, connection_id? }` untuk klien yang hanya bisa mengirim JSON (seluruh body JSON maks 12 MB). Prefix data URL (`data:...;base64,`) dibuang otomatis.
Tipe MIME diambil dari header Content-Type bila bukan octet-stream/JSON, selain itu ditebak dari ekstensi `filename`. Batas penyedia: Telegram 50 MB per berkas. Folder induk yang belum ada dibuat otomatis di S3 dan Telegram. Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 isi kosong atau filename tidak valid (kosong atau mengandung "/"); 413 lebih dari 50 MB.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `path` | query | string | tidak | Folder tujuan. Bawaan `/`. Boleh juga di body JSON. default: /; contoh: /catatan |
| `filename` | query | string | tidak | Nama berkas (wajib, di query atau body JSON), maks 255 karakter, tanpa "/". contoh: todo.txt |
| `connection_id` | query | string | tidak | Id koneksi drive (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Body JSON:

```json
{
  "path": "/catatan",
  "filename": "todo.txt",
  "content_base64": "LSBLaXJpbSBmYWt0dXIgSU5WLTExODcK"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/drive/upload?path=%2Fcatatan&filename=todo.txt&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path":"/catatan","filename":"todo.txt","content_base64":"LSBLaXJpbSBmYWt0dXIgSU5WLTExODcK"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berkas berhasil diunggah",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "item": {
      "name": "todo.txt",
      "path": "/catatan/todo.txt",
      "is_folder": false,
      "size": 27,
      "mime_type": "text/plain",
      "modified_at": "2026-11-12T09:15:03.221Z"
    }
  }
}
```

### POST /drive/folders

**Buat folder**

- URL: `POST https://api.apipedia.id/v1/drive/folders`
- Auth: `Authorization: Bearer <token>`
- operationId: `createDriveFolder`

Membuat folder baru di `path` (path lengkap folder baru, mis. `/laporan/2026`). Di S3 folder adalah objek penanda kosong berakhiran "/"; di Telegram folder induk ikut dibuat; di pCloud folder yang sudah ada tidak dianggap galat; di Dropbox dan OneDrive folder bernama sama menghasilkan 409 dari penyedia. Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 path kosong/akar atau nama tidak valid; 404 folder induk tidak ditemukan (MEGA).

Body JSON:

```json
{
  "path": "/laporan/2026",
  "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/drive/folders" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path":"/laporan/2026","connection_id":"3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Folder berhasil dibuat",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "item": {
      "name": "2026",
      "path": "/laporan/2026",
      "is_folder": true,
      "size": 0,
      "mime_type": null,
      "modified_at": null
    }
  }
}
```

### POST /drive/rename

**Ganti nama berkas atau folder**

- URL: `POST https://api.apipedia.id/v1/drive/rename`
- Auth: `Authorization: Bearer <token>`
- operationId: `renameDrivePath`

Mengganti nama berkas atau folder di `path` menjadi `name` (tetap di folder yang sama; bukan untuk memindahkan). Folder ikut membawa seluruh isinya (di S3 setiap objek disalin lalu yang lama dihapus). Path selalu berbentuk `/folder/sub/berkas.ext` yang sama untuk semua penyedia (diawali `/`, tanpa `..`, tiap bagian maks 255 karakter).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.
Error lain: 400 path kosong/akar atau name kosong/mengandung "/"; 404 path tidak ditemukan; 409 nama tujuan sudah ada (Dropbox).

Body JSON:

```json
{
  "path": "/laporan/2026/faktur-november.pdf",
  "name": "faktur-nov-2026.pdf",
  "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/drive/rename" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path":"/laporan/2026/faktur-november.pdf","name":"faktur-nov-2026.pdf","connection_id":"3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Nama berhasil diganti",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "from": "/laporan/2026/faktur-november.pdf",
    "to": "/laporan/2026/faktur-nov-2026.pdf"
  }
}
```

### GET /drive/quota

**Kuota penyimpanan**

- URL: `GET https://api.apipedia.id/v1/drive/quota`
- Auth: `Authorization: Bearer <token>`
- operationId: `getDriveQuota`

Kapasitas terpakai dan total akun, dalam byte. `quota` null untuk S3 dan yang kompatibel (tidak punya kuota tetap; pesan "Penyedia ini tidak memiliki kuota tetap"). Telegram melaporkan total ukuran berkas yang diunggah lewat APIPedia dengan `total: 0` (tanpa batas).
Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Drive. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `connection_id` | query | string | tidak | Id koneksi drive (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. contoh: 3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/quota?connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kuota berhasil diambil",
  "data": {
    "connection_id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
    "quota": {
      "used": 734003200,
      "total": 2147483648
    }
  }
}
```

### GET /drive/search

**Cari berkas di semua akun**

- URL: `GET https://api.apipedia.id/v1/drive/search`
- Auth: `Authorization: Bearer <token>`
- operationId: `searchDriveFiles`

Mencari berkas/folder yang namanya mengandung `q` di SEMUA akun drive Anda sekaligus (maks 20 akun), atau di satu akun bila `connection_id` diisi. Maks 100 hasil per akun, batas waktu 25 detik per akun. Setiap hasil membawa `connection_id`, `provider`, dan `account_label` akunnya. Akun yang gagal TIDAK menggagalkan hasil akun lain: dilaporkan di `errors` (`connection_id`, `status`, `message`; 504 bila terlalu lama). S3 dan Yandex Disk memindai daftar berkas lalu menyaring nama; penyedia lain memakai pencarian bawaannya.
Error: 400 q kurang dari 2 karakter atau connection_id bukan uuid.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Kata kunci nama berkas, 2-100 karakter. Alias: `query`. contoh: faktur |
| `connection_id` | query | string | tidak | Batasi pencarian ke satu akun drive (uuid). Kosongkan untuk mencari di semua akun. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/drive/search?q=faktur" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian",
  "data": {
    "q": "faktur",
    "items": [
      {
        "name": "faktur-november.pdf",
        "path": "/laporan/2026/faktur-november.pdf",
        "is_folder": false,
        "size": 248113,
        "mime_type": "application/pdf",
        "modified_at": "2026-11-12T03:15:42.000Z",
        "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
        "provider": "s3",
        "account_label": "arsip-toko (abc123.r2.cloudflarestorage.com)"
      },
      {
        "name": "faktur-oktober.pdf",
        "path": "/Dokumen/faktur-oktober.pdf",
        "is_folder": false,
        "size": 231877,
        "mime_type": "application/pdf",
        "modified_at": "2026-10-31T10:04:19.000Z",
        "connection_id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
        "provider": "dropbox",
        "account_label": "budi@tokosejahtera.co.id"
      }
    ],
    "errors": []
  }
}
```

### GET /drive-oauth/{provider}/callback

**Callback OAuth Dropbox/OneDrive/Yandex Disk (internal)**

- URL: `GET https://api.apipedia.id/v1/drive-oauth/{provider}/callback`
- **INTERNAL** - dipanggil pihak ketiga, bukan oleh agent
- Auth: tidak perlu token
- operationId: `driveOAuthCallback`

Redirect URI yang didaftarkan di penyedia. PUBLIK, tanpa token: identitas pengguna diambil dari `state` bertanda tangan HMAC (berlaku 15 menit) hasil POST /drive/oauth/{provider}/start. Menukar `code` menjadi token, menyimpan akun, lalu SELALU menjawab 302 ke halaman Koneksi console (`?toolkit=drive-<provider>&status=active` atau `status=failed&reason=...`). Jangan dipanggil langsung.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `provider` | path | string | ya | Penyedia OAuth: `dropbox`, `onedrive`, atau `yandex`. pilihan: dropbox, onedrive, yandex; contoh: dropbox |
| `code` | query | string | tidak | Kode otorisasi dari penyedia. |
| `state` | query | string | ya | State bertanda tangan dari langkah start. |

Contoh respons:

```json
{
  "is_success": true,
  "message": "302 ke console.apipedia.id/connections?toolkit=drive-dropbox&status=active",
  "data": null
}
```

## Dokumen

HTML to PDF (ubah HTML atau URL publik menjadi PDF) dan Docs to PDF (ekspor Google Docs lewat akun Google yang terhubung)

### GET /toolkits/googledocs/documents/{documentId}/pdf

**Ekspor dokumen Google Docs ke PDF**

- URL: `GET https://api.apipedia.id/v1/toolkits/googledocs/documents/{documentId}/pdf`
- Auth: `Authorization: Bearer <token>`
- operationId: `exportGoogleDocPdf`

Docs to PDF: mengekspor satu dokumen Google Docs milik akun Google yang terhubung menjadi PDF. Dieksekusi lewat tool Composio `GOOGLEDOCS_EXPORT_DOCUMENT_AS_PDF` (Google Drive API files.export dengan mimeType application/pdf), memakai koneksi toolkit `googledocs` yang sama dengan POST /toolkits/googledocs/documents. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum, atau bila ada beberapa akun tanpa default - sebutkan akunnya lewat header `X-Connection-Id` atau query `connection_id`).
`documentId` adalah bagian `/d/{documentId}/` dari URL Google Docs. Akun yang terhubung harus punya akses ke dokumen itu.
`output`:
- `url` (bawaan): amplop JSON berisi `downloadUrl` berumur pendek di penyimpanan Composio (contoh di bawah). Unduh segera.
- `binary`: berkas PDF mentah (`application/pdf`, `Content-Disposition: attachment`), diunduh server dari tautan tadi.
- `base64`: amplop JSON `{ documentId, filename, size, base64 }`.
Google membatasi hasil ekspor 10 MB per dokumen. Error: 400 documentId/output tidak valid; 409 koneksi belum aktif/akun ambigu; 502 Google/Composio menolak (mis. dokumen tidak ditemukan atau tanpa akses); 503 COMPOSIO_API_KEY server kosong.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `documentId` | path | string | ya | Id dokumen Google Docs (dari URL https://docs.google.com/document/d/{documentId}/edit). contoh: 1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789 |
| `output` | query | string | tidak | `url` (bawaan), `binary`, atau `base64`. pilihan: url, binary, base64; default: url; contoh: url |
| `filename` | query | string | tidak | Nama berkas PDF yang disarankan. Bawaan: `<documentId>.pdf`. contoh: notulen-rapat.pdf |
| `connection_id` | query | string | tidak | Id akun Google (dari GET /connections) bila Anda punya lebih dari satu akun Google Docs. Kosongkan untuk memakai akun bawaan. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/googledocs/documents/1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789/pdf?output=url&filename=notulen-rapat.pdf" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Dokumen Google Docs berhasil diekspor ke PDF",
  "data": {
    "documentId": "1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789",
    "filename": "notulen-rapat.pdf",
    "mimeType": "application/pdf",
    "size": 58312,
    "downloadUrl": "https://temp.composio.dev/files/export/notulen-rapat.pdf?X-Amz-Expires=3600&X-Amz-Signature=...",
    "displayUrl": "https://docs.google.com/document/d/1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789/edit",
    "expiresNote": "downloadUrl berumur pendek (dari penyimpanan Composio); unduh segera atau pakai output=binary."
  }
}
```

### POST /pdf/html

**Render HTML atau URL publik menjadi PDF**

- URL: `POST https://api.apipedia.id/v1/pdf/html`
- Auth: `Authorization: Bearer <token>`
- operationId: `convertHtmlToPdf`

Mengubah HTML mentah atau halaman web publik menjadi PDF di server compute. Teks hasilnya tetap bisa diseleksi dan dicari karena PDF dibangun dari teks, bukan dari tangkapan layar.
Body: isi TEPAT SATU dari `html` (string, maks 2 MB) atau `url` (http/https publik). Opsi: `format` (A4 bawaan, Letter, Legal, A3, A5), `landscape` (boolean), `margin` `{top,right,bottom,left}` dengan satuan px/in/cm/mm (bawaan 10mm), `displayHeaderFooter` + `headerTemplate`/`footerTemplate` (HTML maks 20 KB; teksnya dipakai apa adanya, dan penanda `{page}` serta `{pages}` diganti nomor halaman), `filename` (nama berkas unduhan).
CAKUPAN CSS - baca sebelum memakai: mesin render ini bukan browser. Flexbox, CSS Grid, dan stylesheet eksternal (`<link rel=stylesheet>`) TIDAK didukung; yang terbaca adalah style inline dan `<style>` di dokumen, untuk properti umum seperti warna, border, font, margin, perataan, lebar, dan tinggi. Cocok untuk invoice, kuitansi, laporan, sertifikat, dan tabel. Halaman dengan tata letak rumit tetap menghasilkan PDF, tapi posisinya akan bergeser dari tampilan aslinya di browser.
JavaScript TIDAK PERNAH dijalankan, jadi halaman yang isinya digambar JavaScript akan menghasilkan PDF kosong. Opsi `javascript`, `scale`, dan `printBackground` sudah tidak berlaku dan diabaikan bila dikirim.
Respons bawaan adalah berkas PDF mentah (`Content-Type: application/pdf`, `Content-Disposition: attachment; filename=...`). Tambahkan `?output=base64` (atau field `output: "base64"`) untuk amplop JSON `{ filename, size, maxPages, base64 }` seperti contoh di bawah.
Keamanan dan batas: gambar diambil oleh server (bukan oleh pustaka PDF) hanya dari https publik dalam format JPEG atau PNG, maks 5 MB per gambar dan 50 gambar; gambar yang gagal diambil dibuang tanpa menggagalkan render. Alamat privat, loopback, link-local, metadata cloud, dan host internal selalu ditolak, termasuk sesudah resolve DNS. Skema lain (file:, ftp:, dsb.) ditolak. Batas: 20 detik total (504), maks 100 halaman (kelebihannya dipotong), PDF maks 20 MB. Antrean penuh dijawab 503 dengan `Retry-After`.
Error: 400 masukan tidak valid / URL privat / halaman gagal diambil; 503 mesin PDF sibuk; 504 melebihi 20 detik.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `output` | query | string | tidak | `pdf` (bawaan, berkas application/pdf) atau `base64` (amplop JSON). pilihan: pdf, base64; default: pdf; contoh: base64 |

Body JSON:

```json
{
  "html": "<h1>Invoice #INV-2026-001</h1><p>Total: <b>Rp 25.000</b></p>",
  "format": "A4",
  "landscape": false,
  "margin": {
    "top": "20mm",
    "right": "15mm",
    "bottom": "20mm",
    "left": "15mm"
  },
  "displayHeaderFooter": true,
  "footerTemplate": "<div style=\"font-size:8px;text-align:center\">{page} / {pages}</div>",
  "filename": "invoice-2026-001.pdf"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/pdf/html?output=base64" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Invoice #INV-2026-001</h1><p>Total: <b>Rp 25.000</b></p>","format":"A4","landscape":false,"margin":{"top":"20mm","right":"15mm","bottom":"20mm","left":"15mm"},"displayHeaderFooter":true,"footerTemplate":"<div style=\"font-size:8px;text-align:center\">{page} / {pages}</div>","filename":"invoice-2026-001.pdf"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "PDF berhasil dibuat",
  "data": {
    "filename": "invoice-2026-001.pdf",
    "size": 48213,
    "maxPages": 100,
    "base64": "JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoK..."
  }
}
```

## Email Tracking

Piksel pelacak 1x1 untuk mengetahui apakah email sudah dibuka. Piksel publik tanpa token dan tidak memotong kuota per hit.

### GET /track/emails

**Daftar pelacak email**

- URL: `GET https://api.apipedia.id/v1/track/emails`
- Auth: `Authorization: Bearer <token>`
- operationId: `listEmailTrackers`

Pelacak milik pemanggil, terbaru dulu, berhalaman (`page` mulai 1, `pageSize` 1-100, bawaan 20). Filter opsional `opened=true|false` dan pencarian `q` (cocok sebagian di `label` atau `recipient`, tanpa beda huruf besar/kecil). Setiap item membawa `status`: `scheduled` (sebelum waktu mulai), `waiting` (belum dibuka), `opened` (minimal satu buka terhitung), atau `expired` (lewat `expiresAt` tanpa buka terhitung). `webhookSecret` tidak pernah ikut.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `page` | query | integer | tidak | default: 1 |
| `pageSize` | query | integer | tidak | default: 20 |
| `opened` | query | string | tidak | true = hanya yang sudah dibuka, false = hanya yang belum. pilihan: true, false |
| `q` | query | string | tidak | Cari di label atau recipient (maks 100 karakter). |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/track/emails" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar pelacak email berhasil diambil",
  "data": {
    "items": [
      {
        "id": "7hQx2LmPz9RkT4vWb8NcJd",
        "label": "Promo Oktober",
        "recipient": "budi@contoh.com",
        "metadata": {
          "campaign": "okt-2026"
        },
        "status": "opened",
        "opened": true,
        "openCount": 2,
        "humanOpenCount": 1,
        "likelyProxyOnly": false,
        "firstOpenedAt": "2026-10-29T03:20:11Z",
        "lastOpenedAt": "2026-10-29T08:02:45Z",
        "startAt": "2026-10-29T03:20:00Z",
        "expiresAt": null,
        "countProxyAsOpen": true,
        "webhook": {
          "url": "https://contoh.com/webhooks/apipedia",
          "events": "first",
          "lastDeliveryAt": "2026-10-29T03:20:12Z",
          "lastStatus": "200"
        },
        "createdAt": "2026-10-29T03:15:00Z",
        "pixelUrl": "https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif",
        "htmlSnippet": "<img src=\"https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif\" width=\"1\" height=\"1\" alt=\"\" style=\"display:none\">",
        "statusUrl": "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd"
      }
    ],
    "page": 1,
    "pageSize": 20,
    "total": 1
  }
}
```

### POST /track/emails

**Buat pelacak email (dapatkan URL piksel)**

- URL: `POST https://api.apipedia.id/v1/track/emails`
- Auth: `Authorization: Bearer <token>`
- operationId: `createEmailTracker`

Langkah 1 Email Tracking: membuat id pelacak acak (22 karakter base62, tidak berurutan dan tidak bisa ditebak) untuk SATU email. Sematkan `htmlSnippet` (atau `<img>` ke `pixelUrl`) di HTML email yang Anda kirim; setiap kali klien email memuat gambar itu, buka-nya tercatat. Cek hasilnya lewat `statusUrl` (GET /track/emails/{id}).
Body (semua opsional):
- `label` (maks 200 karakter), `recipient` (maks 320 karakter), `metadata` (objek JSON bebas, maks 2 KB).
- Jadwal mulai - isi SALAH SATU: `startAfterMinutes` (bilangan bulat 0-525600) atau `startAt` (ISO 8601 WAJIB dengan zona waktu, mis. `2026-11-01T09:00:00+07:00`; waktu lampau = sekarang). Bawaan: mulai sekarang. Buka sebelum waktu mulai (mis. Anda membuka email di folder Terkirim) tetap tercatat sebagai event dengan `ignoredReason: "before_start"` tetapi tidak dihitung.
- `expiresAt` (ISO 8601 dengan zona waktu, di masa depan, sesudah waktu mulai): buka sesudahnya dicatat dengan `ignoredReason: "expired"` dan tidak dihitung.
- `countProxyAsOpen` (bawaan `true`): `false` = buka dari proxy gambar (`likelyProxy`) tidak mengubah status menjadi dibuka dan tidak dihitung di `openCount`.
- `webhookUrl` (https saja, maks 2048 karakter, host privat/lokal ditolak) dan `webhookEvents`: `first` (bawaan, hanya buka terhitung pertama) atau `every` (setiap buka terhitung).
Bila `webhookUrl` diisi, respons membawa `webhookSecret` (`apwh_...`) SATU KALI SAJA - simpan, karena tidak pernah ditampilkan lagi (putar lewat PATCH `rotateWebhookSecret: true`).
**Webhook.** Pada buka yang terhitung (di dalam jendela mulai/kedaluwarsa, tidak dikecualikan, lolos aturan proxy) APIPedia mengirim `POST` JSON ke `webhookUrl`:
```json
{"event":"email.opened","trackerId":"7hQx2LmPz9RkT4vWb8NcJd","eventId":42,"label":"Promo Oktober","recipient":"budi@contoh.com","metadata":{"campaign":"okt-2026"},"openedAt":"2026-10-29T03:20:11Z","openCount":1,"firstOpen":true,"likelyProxy":false,"userAgent":"Mozilla/5.0 ... Thunderbird/128.0"}
```
Header: `X-APIPedia-Event: email.opened`, `X-APIPedia-Delivery: <uuid>` (sama untuk percobaan ulang - pakai untuk membuang duplikat), `X-APIPedia-Signature: sha256=<hex>`. Tanda tangan = HMAC-SHA256 dari BODY MENTAH (byte persis yang diterima, sebelum di-parse) dengan `webhookSecret` sebagai kunci, ditulis hex huruf kecil. Verifikasi (Node.js):
```js
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-apipedia-signature']));
```
Pengiriman berjalan sesudah piksel dijawab, timeout 5 detik, 1x percobaan ulang untuk error jaringan/timeout/408/429/5xx, redirect tidak diikuti. Balas 2xx secepatnya. Hasil terakhir tampil di `webhook.lastStatus` (kode HTTP, `timeout`, `network_error`, `blocked_url`, atau `blocked_address` bila host me-resolve ke alamat privat).
Catatan penting: piksel hanya bisa membuktikan GAMBAR dimuat. Klien yang memblokir gambar membuat email yang sudah dibaca tetap tercatat belum dibuka ("belum dibuka" bukan berarti "belum dibaca"). Sebaliknya, proxy gambar Gmail (GoogleImageProxy) dan Apple Mail Privacy Protection bisa memuat piksel secara otomatis tanpa email dibuka manusia; event seperti itu ditandai `likelyProxy: true`.

Body JSON (opsional):

```json
{
  "label": "Promo Oktober",
  "recipient": "budi@contoh.com",
  "metadata": {
    "campaign": "okt-2026",
    "userId": 42
  },
  "startAfterMinutes": 5,
  "expiresAt": "2026-11-30T00:00:00+07:00",
  "countProxyAsOpen": true,
  "webhookUrl": "https://contoh.com/webhooks/apipedia",
  "webhookEvents": "first"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/track/emails" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"Promo Oktober","recipient":"budi@contoh.com","metadata":{"campaign":"okt-2026","userId":42},"startAfterMinutes":5,"expiresAt":"2026-11-30T00:00:00+07:00","countProxyAsOpen":true,"webhookUrl":"https://contoh.com/webhooks/apipedia","webhookEvents":"first"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pelacak email berhasil dibuat",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "label": "Promo Oktober",
    "recipient": "budi@contoh.com",
    "metadata": {
      "campaign": "okt-2026",
      "userId": 42
    },
    "status": "scheduled",
    "opened": false,
    "openCount": 0,
    "humanOpenCount": 0,
    "likelyProxyOnly": false,
    "firstOpenedAt": null,
    "lastOpenedAt": null,
    "startAt": "2026-10-29T03:20:00Z",
    "expiresAt": "2026-11-29T17:00:00Z",
    "countProxyAsOpen": true,
    "webhook": {
      "url": "https://contoh.com/webhooks/apipedia",
      "events": "first",
      "lastDeliveryAt": null,
      "lastStatus": null
    },
    "createdAt": "2026-10-29T03:15:00Z",
    "pixelUrl": "https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif",
    "htmlSnippet": "<img src=\"https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif\" width=\"1\" height=\"1\" alt=\"\" style=\"display:none\">",
    "statusUrl": "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd",
    "webhookSecret": "apwh_CONTOH-rahasia-hanya-tampil-sekali-xxxxxxxx"
  }
}
```

### GET /track/emails/{id}

**Status buka satu email**

- URL: `GET https://api.apipedia.id/v1/track/emails/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getEmailTracker`

Langkah 3 Email Tracking: `status`, `opened`, `openCount`, `humanOpenCount` (buka terhitung yang bukan dari proxy gambar), `likelyProxyOnly` (dibuka tetapi semua bukanya dari proxy), `firstOpenedAt`, `lastOpenedAt` (hanya buka TERHITUNG), jadwal (`startAt`, `expiresAt`), `webhook`, dan 50 event terakhir `{ id, at, userAgent, likelyProxy, ignoredReason, excluded, excludedAt, counted }`. Event dengan `ignoredReason` (`before_start` / `expired`) atau `excluded: true` ("Itu saya") tidak dihitung; begitu juga event proxy bila `countProxyAsOpen` false. `humanOpenSeen` true bila setidaknya satu event terhitung BUKAN dari proxy gambar - bukti terkuat yang bisa diberikan piksel bahwa email dibuka manusia. Alamat IP pembuka tidak pernah disimpan maupun dikembalikan. Hanya pemilik pelacak yang bisa membacanya (id milik orang lain dijawab 404).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status pelacak email berhasil diambil",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "label": "Promo Oktober",
    "recipient": "budi@contoh.com",
    "metadata": {
      "campaign": "okt-2026"
    },
    "status": "opened",
    "opened": true,
    "openCount": 2,
    "humanOpenCount": 1,
    "likelyProxyOnly": false,
    "firstOpenedAt": "2026-10-29T03:25:40Z",
    "lastOpenedAt": "2026-10-29T08:02:45Z",
    "startAt": "2026-10-29T03:20:00Z",
    "expiresAt": null,
    "countProxyAsOpen": true,
    "webhook": {
      "url": "https://contoh.com/webhooks/apipedia",
      "events": "first",
      "lastDeliveryAt": "2026-10-29T03:25:41Z",
      "lastStatus": "200"
    },
    "createdAt": "2026-10-29T03:15:00Z",
    "pixelUrl": "https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif",
    "htmlSnippet": "<img src=\"https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif\" width=\"1\" height=\"1\" alt=\"\" style=\"display:none\">",
    "statusUrl": "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd",
    "humanOpenSeen": true,
    "events": [
      {
        "id": "1042",
        "at": "2026-10-29T08:02:45Z",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Thunderbird/128.0",
        "likelyProxy": false,
        "ignoredReason": null,
        "excluded": false,
        "excludedAt": null,
        "counted": true
      },
      {
        "id": "1041",
        "at": "2026-10-29T03:25:40Z",
        "userAgent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)",
        "likelyProxy": true,
        "ignoredReason": null,
        "excluded": false,
        "excludedAt": null,
        "counted": true
      },
      {
        "id": "1040",
        "at": "2026-10-29T03:16:02Z",
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15",
        "likelyProxy": false,
        "ignoredReason": "before_start",
        "excluded": false,
        "excludedAt": null,
        "counted": false
      }
    ]
  }
}
```

### PATCH /track/emails/{id}

**Ubah pelacak email**

- URL: `PATCH https://api.apipedia.id/v1/track/emails/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `updateEmailTracker`

Hanya field yang DIKIRIM yang berubah. `label`, `recipient`, `metadata`, `expiresAt`, `webhookUrl` boleh `null` untuk menghapus. Jadwal mulai: `startAt` (ISO 8601 dengan zona waktu; boleh di masa lalu, dipotong ke waktu pembuatan pelacak) atau `startAfterMinutes` (dari sekarang); `startAt: null` = mulai sekarang. Mengubah `startAt`, `expiresAt`, atau `countProxyAsOpen` mengklasifikasi ulang SEMUA event tersimpan dan menghitung ulang `openCount`/`firstOpenedAt`/`lastOpenedAt` (webhook tidak dikirim untuk buka lama yang menjadi terhitung). `webhookEvents`: `first` | `every`. `rotateWebhookSecret: true` membuat rahasia baru.
`webhookSecret` di respons hanya terisi bila rahasia BARU dibuat (webhook pertama kali dipasang, atau diputar); selain itu `null`. Menghapus `webhookUrl` ikut menghapus rahasianya. Field lain dijawab 400.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |

Body JSON:

```json
{
  "label": "Promo Oktober (follow-up)",
  "expiresAt": "2026-12-31T23:59:00+07:00",
  "webhookEvents": "every"
}
```

Contoh request:

```bash
curl -X PATCH "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"Promo Oktober (follow-up)","expiresAt":"2026-12-31T23:59:00+07:00","webhookEvents":"every"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pelacak email berhasil diperbarui",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "label": "Promo Oktober (follow-up)",
    "recipient": "budi@contoh.com",
    "metadata": {
      "campaign": "okt-2026"
    },
    "status": "opened",
    "opened": true,
    "openCount": 2,
    "humanOpenCount": 1,
    "likelyProxyOnly": false,
    "firstOpenedAt": "2026-10-29T03:25:40Z",
    "lastOpenedAt": "2026-10-29T08:02:45Z",
    "startAt": "2026-10-29T03:20:00Z",
    "expiresAt": "2026-12-31T16:59:00Z",
    "countProxyAsOpen": true,
    "webhook": {
      "url": "https://contoh.com/webhooks/apipedia",
      "events": "every",
      "lastDeliveryAt": "2026-10-29T03:25:41Z",
      "lastStatus": "200"
    },
    "createdAt": "2026-10-29T03:15:00Z",
    "pixelUrl": "https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif",
    "htmlSnippet": "<img src=\"https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif\" width=\"1\" height=\"1\" alt=\"\" style=\"display:none\">",
    "statusUrl": "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd",
    "webhookSecret": null
  }
}
```

### DELETE /track/emails/{id}

**Hapus pelacak email**

- URL: `DELETE https://api.apipedia.id/v1/track/emails/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `deleteEmailTracker`

Menghapus pelacak beserta seluruh event-nya. Sesudahnya piksel tetap menjawab GIF (supaya email lama tidak menampilkan gambar rusak) tetapi tidak ada yang dicatat lagi.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pelacak email berhasil dihapus",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "deleted": true
  }
}
```

### POST /track/emails/{id}/events/{eventId}/exclude

**Kecualikan satu event ("Itu saya")**

- URL: `POST https://api.apipedia.id/v1/track/emails/{id}/events/{eventId}/exclude`
- Auth: `Authorization: Bearer <token>`
- operationId: `excludeEmailTrackerEvent`

Menandai satu event sebagai buka oleh Anda sendiri (mis. pratinjau di folder Terkirim). Event tetap tersimpan dan terlihat dengan `excluded: true`, tetapi tidak dihitung: `openCount`, `firstOpenedAt`, `lastOpenedAt`, dan `status` dihitung ulang dari event yang tersisa. Respons = detail pelacak yang sudah diperbarui (bentuk sama dengan GET /track/emails/{id}). Event milik pelacak lain atau id yang tidak ada dijawab 404. Webhook yang sudah terkirim tidak ditarik.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |
| `eventId` | path | string | ya | Id event dari `events[].id` di GET /track/emails/{id}. contoh: 1042 |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd/events/1042/exclude" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Event dikecualikan dari hitungan",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "status": "opened",
    "opened": true,
    "openCount": 1,
    "humanOpenCount": 1,
    "likelyProxyOnly": false,
    "firstOpenedAt": "2026-10-29T03:25:40Z",
    "lastOpenedAt": "2026-10-29T03:25:40Z",
    "humanOpenSeen": false,
    "events": [
      {
        "id": "1042",
        "at": "2026-10-29T08:02:45Z",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Thunderbird/128.0",
        "likelyProxy": false,
        "ignoredReason": null,
        "excluded": true,
        "excludedAt": "2026-10-29T09:00:00Z",
        "counted": false
      }
    ]
  }
}
```

### POST /track/emails/{id}/events/{eventId}/include

**Hitung kembali event yang dikecualikan**

- URL: `POST https://api.apipedia.id/v1/track/emails/{id}/events/{eventId}/include`
- Auth: `Authorization: Bearer <token>`
- operationId: `includeEmailTrackerEvent`

Membatalkan "Itu saya": event kembali dihitung (selama berada di dalam jendela mulai/kedaluwarsa dan lolos aturan proxy), lalu agregat dihitung ulang. Respons = detail pelacak yang sudah diperbarui. Tidak mengirim webhook.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |
| `eventId` | path | string | ya | Id event dari `events[].id` di GET /track/emails/{id}. contoh: 1042 |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/track/emails/7hQx2LmPz9RkT4vWb8NcJd/events/1042/include" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Event dihitung kembali",
  "data": {
    "id": "7hQx2LmPz9RkT4vWb8NcJd",
    "status": "opened",
    "opened": true,
    "openCount": 2,
    "humanOpenCount": 1,
    "likelyProxyOnly": false,
    "firstOpenedAt": "2026-10-29T03:25:40Z",
    "lastOpenedAt": "2026-10-29T08:02:45Z",
    "humanOpenSeen": true,
    "events": [
      {
        "id": "1042",
        "at": "2026-10-29T08:02:45Z",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Thunderbird/128.0",
        "likelyProxy": false,
        "ignoredReason": null,
        "excluded": false,
        "excludedAt": null,
        "counted": true
      }
    ]
  }
}
```

### GET /track/open/{id}.gif

**Piksel pelacak (publik, disematkan di email)**

- URL: `GET https://api.apipedia.id/v1/track/open/{id}.gif`
- Auth: tidak perlu token
- operationId: `emailTrackingPixel`

Langkah 2 Email Tracking ("proses id"): URL inilah yang dimuat klien email. PUBLIK, tanpa token. Selalu menjawab GIF transparan 1x1 (`image/gif`, 43 byte) dengan `Cache-Control: no-store, no-cache, must-revalidate, max-age=0`, `Pragma: no-cache`, `Expires: 0` - juga untuk id yang tidak dikenal atau sudah dihapus (keberadaan id tidak bisa ditebak dari respons; hit seperti itu tidak dicatat).
Setiap hit mencatat waktu, user agent, hash SHA-256 IP (IP mentah tidak disimpan), dan `likelyProxy` (GoogleImageProxy, Apple Mail Privacy Protection, YahooMailProxy, user agent kosong). Hit ulang dari IP dan user agent yang sama dalam 10 detik dibuang, dan lebih dari 60 hit per menit per pelacak diabaikan. Buka sebelum `startAt` atau sesudah `expiresAt` dicatat sebagai event yang diabaikan (`ignoredReason`) dan tidak memicu webhook. Hit piksel TIDAK dihitung ke kuota atau log pemilik pelacak. Anda tidak perlu memanggil endpoint ini sendiri.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id pelacak (22 karakter) dari POST /track/emails. contoh: 7hQx2LmPz9RkT4vWb8NcJd |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/track/open/7hQx2LmPz9RkT4vWb8NcJd.gif"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Berhasil",
  "data": null
}
```

## Keamanan

Hash SHA-2 (sha256, sha384, sha512)

### POST /crypto/hash

**Hash data dengan algoritma pilihan**

- URL: `POST https://api.apipedia.id/v1/crypto/hash`
- Auth: `Authorization: Bearer <token>`
- operationId: `hashData`

Menghitung hash hex dari teks UTF-8. `algorithm` hanya menerima sha256 (default), sha384, atau sha512; md5 dan sha1 sengaja ditolak karena sudah tidak aman terhadap tumbukan. `input` wajib berupa string (boleh kosong).

Body JSON:

```json
{
  "algorithm": "sha256",
  "input": "data-rahasia"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/crypto/hash" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"algorithm":"sha256","input":"data-rahasia"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hash berhasil dibuat",
  "data": {
    "algorithm": "sha256",
    "hash": "25cee12968b254eee56b5005ade1422310a7219991a44eccc786dc1412a51082"
  }
}
```

## Authenticator

TOTP/HOTP Authenticator — secret dan QR enrolment kompatibel Google Authenticator, verifikasi kode TOTP (RFC 6238) dan HOTP (RFC 4226), serta kode cadangan sekali pakai. Stateless: APIPedia tidak menyimpan secret maupun kode cadangan

### POST /otp/enroll

**Buat secret, URI otpauth://, dan QR enrolment**

- URL: `POST https://api.apipedia.id/v1/otp/enroll`
- Auth: `Authorization: Bearer <token>`
- operationId: `otpEnroll`

Langkah pertama pemasangan 2FA. Menghasilkan secret base32 (160 bit acak dari CSPRNG, RFC 4648 tanpa padding) atau memakai `secret` milik Anda sendiri, merangkai Key URI format Google Authenticator (`otpauth://totp/ISSUER:ACCOUNT?secret=...&issuer=...&algorithm=...&digits=...&period=...`), dan merender QR-nya untuk dipindai Google Authenticator, Microsoft Authenticator, Authy, 1Password, dan sejenisnya.
**APIPedia TIDAK menyimpan secret.** Endpoint ini stateless: simpan `secret` di basis data Anda (sebaiknya terenkripsi), karena secret wajib dikirim ulang ke `POST /otp/verify`. Respons tidak pernah di-cache (`Cache-Control: no-store`) dan setiap panggilan tanpa `secret` menghasilkan secret baru.
Body: `account` wajib (label, mis. email; maks 128 karakter, tanpa `:`); `issuer` (bawaan `APIPedia`, maks 64, tanpa `:`; isi dengan nama aplikasi Anda); `type` `totp` (bawaan) atau `hotp`; `algorithm` `SHA1` (bawaan - satu-satunya yang didukung semua aplikasi, termasuk Google Authenticator), `SHA256`, atau `SHA512`; `digits` 6 (bawaan) atau 8; `period` 15-120 detik (bawaan 30, hanya totp); `counter` awal (bawaan 0, hanya hotp); `secret` opsional (base32, minimal 128 bit / 26 karakter); `qrFormat` `png` (bawaan, 256 px) atau `svg`. `qr` selalu berupa data URL yang bisa langsung dipasang di `<img src>`.
Alur yang disarankan: enroll -> tampilkan `qr` -> minta pengguna memasukkan satu kode -> `POST /otp/verify` -> baru tandai 2FA aktif dan simpan secret-nya.

Body JSON:

```json
{
  "account": "budi@contoh.id",
  "issuer": "TokoBudi",
  "type": "totp",
  "algorithm": "SHA1",
  "digits": 6,
  "period": 30
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/otp/enroll" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"account":"budi@contoh.id","issuer":"TokoBudi","type":"totp","algorithm":"SHA1","digits":6,"period":30}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Secret OTP berhasil dibuat. APIPedia TIDAK menyimpan secret ini - simpan di sisi Anda (terenkripsi) karena wajib dikirim ulang saat verifikasi.",
  "data": {
    "secret": "X3WKFDH2KBNBF2MXZHJNI2DI2VIJTJK7",
    "otpauthUrl": "otpauth://totp/TokoBudi:budi%40contoh.id?secret=X3WKFDH2KBNBF2MXZHJNI2DI2VIJTJK7&issuer=TokoBudi&algorithm=SHA1&digits=6&period=30",
    "qr": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAA...",
    "qrFormat": "png",
    "type": "totp",
    "issuer": "TokoBudi",
    "account": "budi@contoh.id",
    "algorithm": "SHA1",
    "digits": 6,
    "period": 30,
    "secretSource": "generated",
    "stored": false
  }
}
```

### POST /otp/verify

**Verifikasi kode TOTP atau HOTP**

- URL: `POST https://api.apipedia.id/v1/otp/verify`
- Auth: `Authorization: Bearer <token>`
- operationId: `otpVerify`

Mencocokkan kode dari aplikasi autentikator dengan `secret` yang Anda simpan saat enroll. TOTP mengikuti RFC 6238 (langkah waktu `floor(unix / period)`), HOTP mengikuti RFC 4226 (HMAC + dynamic truncation). Pembandingan kode constant-time, dan seluruh jendela toleransi selalu dihitung tanpa berhenti lebih awal.
Body: `secret` wajib (base32; spasi, tanda hubung, dan huruf kecil diterima; minimal 80 bit demi secret lama); `code` wajib berupa TEKS angka sepanjang `digits` (angka JSON ditolak karena nol di depan hilang); `type`, `algorithm`, `digits`, `period` harus sama dengan saat enroll; `counter` (hotp) counter tersimpan; `window` 0-10 (bawaan 1); `timestamp` opsional (detik Unix, totp, bawaan jam server - untuk pengujian).
Toleransi: TOTP memeriksa langkah `-window..+window`; `delta` -1 berarti kode dari 30 detik sebelumnya. HOTP hanya memeriksa ke depan (`counter..counter+window`) karena counter perangkat tidak bisa mundur; bila valid, SIMPAN `nextCounter` sebagai counter baru.
Kode salah BUKAN error: jawabannya HTTP 200 dengan `valid: false` dan `delta: null`. HTTP 400 hanya untuk masukan rusak.
Anti-replay menjadi tanggung jawab Anda karena endpoint ini stateless: untuk TOTP simpan `matchedStep` terakhir yang diterima dan tolak kode berikutnya bila `matchedStep` tidak lebih besar; untuk HOTP cukup selalu memakai `nextCounter`. Batasi juga jumlah percobaan per pengguna.

Body JSON:

```json
{
  "secret": "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ",
  "code": "94287082",
  "type": "totp",
  "algorithm": "SHA1",
  "digits": 8,
  "period": 30,
  "window": 1,
  "timestamp": 89
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/otp/verify" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret":"GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ","code":"94287082","type":"totp","algorithm":"SHA1","digits":8,"period":30,"window":1,"timestamp":89}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kode OTP valid.",
  "data": {
    "valid": true,
    "delta": -1,
    "timeStep": 2,
    "matchedStep": 1,
    "type": "totp",
    "algorithm": "SHA1",
    "digits": 8,
    "period": 30,
    "window": 1
  }
}
```

### POST /otp/backup-codes

**Terbitkan kode cadangan sekali pakai beserta hash-nya**

- URL: `POST https://api.apipedia.id/v1/otp/backup-codes`
- Auth: `Authorization: Bearer <token>`
- operationId: `otpBackupCodes`

Kode cadangan untuk pengguna yang kehilangan perangkat autentikatornya. Setiap kode diambil acak (CSPRNG, tanpa bias modulo) dari 31 simbol yang tidak mudah tertukar (`abcdefghjkmnpqrstuvwxyz23456789` - tanpa 0/o, 1/l/i); kode dalam satu set dijamin unik.
Body: `count` 1-20 (bawaan 10); `length` 8-16 karakter (bawaan 10, ~49 bit; `entropyBits` melaporkan kekuatannya); `format` `grouped` (bawaan, dikelompokkan dengan tanda hubung: kelompok 5 untuk panjang 10/15, selain itu kelompok 4) atau `plain`.
`codes` hanya untuk DITAMPILKAN SEKALI ke pengguna (minta mereka mencatat atau mengunduhnya). `hashes[i]` adalah SHA-256 hex dari `codes[i]` yang sudah dinormalisasi (huruf kecil, tanpa spasi dan tanda hubung) - simpan HANYA hash-nya. APIPedia tidak menyimpan keduanya.
Cara verifikasi di sisi Anda: normalisasi masukan pengguna dengan aturan yang sama (`kode.replace(/[\s-]+/g, '').toLowerCase()`), hitung SHA-256 hex-nya, bandingkan dengan hash tersimpan milik pengguna itu secara constant-time, lalu HAPUS hash yang cocok supaya kodenya tidak bisa dipakai dua kali. Untuk lapisan tambahan, Anda boleh meng-HMAC hash ini dengan pepper milik server sebelum menyimpannya.

Body JSON (opsional):

```json
{
  "count": 3,
  "length": 10,
  "format": "grouped"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/otp/backup-codes" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"count":3,"length":10,"format":"grouped"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "3 kode cadangan berhasil dibuat. Tampilkan kode ke pengguna SEKALI saja dan simpan hanya hash-nya; APIPedia tidak menyimpan keduanya.",
  "data": {
    "codes": [
      "wq4as-gja3z",
      "nnzyy-hkf44",
      "pb583-ss4qs"
    ],
    "hashes": [
      "06537940e025d88456fa20c9ebddbf0e80d409caf15730397830bf4ce0e80885",
      "a29ebb92d808e552df0240cf08ff2ea3e0cb5817a33535be88ea279896f7404d",
      "6a6087d81872388ee239c2493b6c9e25685144c40fc2adc5d12fb4cf2576efec"
    ],
    "hashAlgorithm": "sha256",
    "normalization": "huruf kecil, tanpa spasi dan tanda hubung",
    "count": 3,
    "length": 10,
    "format": "grouped",
    "entropyBits": 49,
    "stored": false
  }
}
```

## Lokasi

IP Geolocation (ip-api.com)

### GET /ip/lookup/{ip}

**Cari detail satu alamat IP**

- URL: `GET https://api.apipedia.id/v1/ip/lookup/{ip}`
- Auth: `Authorization: Bearer <token>`
- operationId: `lookupIp`

Lokasi perkiraan, ISP, dan penanda proxy/hosting untuk satu alamat IPv4 atau IPv6, bersumber dari ip-api.com. Bentuk IP divalidasi lebih dulu (400 kalau bukan IP sah); kalau sumber gagal menjawab, dibalas 502. Jawaban di-cache 1 jam.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `ip` | path | string | ya | Alamat IPv4 atau IPv6. contoh: 182.253.10.4 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ip/lookup/182.253.10.4" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail IP berhasil diambil",
  "data": {
    "ip": "182.253.10.4",
    "country": "Indonesia",
    "region": "Jakarta",
    "city": "Jakarta",
    "isp": "PT Telkom Indonesia",
    "organization": "PT Telkom Indonesia",
    "latitude": -6.2088,
    "longitude": 106.8456,
    "timezone": "Asia/Jakarta",
    "is_proxy": false,
    "is_hosting": false
  }
}
```

## Maps

Geocoding dan reverse geocoding di atas OpenStreetMap Nominatim (ODbL)

### GET /maps/geocode

**Alamat menjadi koordinat**

- URL: `GET https://api.apipedia.id/v1/maps/geocode`
- Auth: `Authorization: Bearer <token>`
- operationId: `mapsGeocode`

Mencari koordinat dari nama tempat atau alamat lewat OpenStreetMap Nominatim, maksimal 5 hasil. Jawaban di-cache 24 jam dan panggilan ke Nominatim dibatasi satu per detik sesuai kebijakan layanannya. Data dari OpenStreetMap contributors (ODbL).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama tempat atau alamat, minimal 3 karakter. contoh: Monas Jakarta |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/maps/geocode?q=Monas%20Jakarta" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil geocoding berhasil diambil. Data © OpenStreetMap contributors (ODbL).",
  "data": {
    "q": "Monas Jakarta",
    "jumlah": 1,
    "hasil": [
      {
        "lat": -6.1753924,
        "lon": 106.8271528,
        "display_name": "Monumen Nasional, Gambir, Jakarta Pusat, DKI Jakarta, Indonesia",
        "type": "attraction",
        "address": {
          "road": "Lapangan Monas",
          "village": null,
          "suburb": "Gambir",
          "city_district": null,
          "city": "Jakarta",
          "county": "Jakarta Pusat",
          "state": "DKI Jakarta",
          "postcode": "10110",
          "country": "Indonesia"
        }
      }
    ],
    "cached": false
  }
}
```

### GET /maps/reverse

**Koordinat menjadi alamat**

- URL: `GET https://api.apipedia.id/v1/maps/reverse`
- Auth: `Authorization: Bearer <token>`
- operationId: `mapsReverse`

Mencari alamat terdekat dari sepasang koordinat lewat OpenStreetMap Nominatim. Jawaban di-cache 24 jam. Data dari OpenStreetMap contributors (ODbL).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `lat` | query | number | ya | Lintang, -90 sampai 90. contoh: -6.1754 |
| `lon` | query | number | ya | Bujur, -180 sampai 180. contoh: 106.8272 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/maps/reverse?lat=-6.1754&lon=106.8272" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Alamat berhasil diambil dari koordinat. Data © OpenStreetMap contributors (ODbL).",
  "data": {
    "lat": -6.1753924,
    "lon": 106.8271528,
    "display_name": "Monumen Nasional, Gambir, Jakarta Pusat, DKI Jakarta, Indonesia",
    "type": "attraction",
    "address": {
      "road": "Lapangan Monas",
      "village": null,
      "suburb": "Gambir",
      "city_district": null,
      "city": "Jakarta",
      "county": "Jakarta Pusat",
      "state": "DKI Jakarta",
      "postcode": "10110",
      "country": "Indonesia"
    },
    "cached": false
  }
}
```

## Hari Libur

Hari Libur Nasional Indonesia

### GET /libur/tahun/{tahun}

**Daftar hari libur nasional satu tahun**

- URL: `GET https://api.apipedia.id/v1/libur/tahun/{tahun}`
- Auth: `Authorization: Bearer <token>`
- operationId: `listHolidaysByYear`

Mengembalikan seluruh hari libur nasional dan cuti bersama pada tahun yang diminta, sesuai Surat Keputusan Bersama tiga menteri. `jenis` bernilai `libur_nasional` atau `cuti_bersama`. Saat ini data baru mencakup tahun 2026; tahun lain dijawab 400 dengan daftar tahun yang tersedia.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `tahun` | path | integer | ya | Tahun empat digit. contoh: 2026 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/libur/tahun/2026" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar hari libur tahun 2026 berhasil diambil",
  "data": {
    "tahun": 2026,
    "jumlah": 25,
    "libur": [
      {
        "tanggal": "2026-01-01",
        "keterangan": "Tahun Baru Masehi 2026",
        "jenis": "libur_nasional",
        "hari": "Kamis"
      },
      {
        "tanggal": "2026-02-16",
        "keterangan": "Cuti Bersama Tahun Baru Imlek",
        "jenis": "cuti_bersama",
        "hari": "Senin"
      },
      {
        "tanggal": "2026-02-17",
        "keterangan": "Tahun Baru Imlek 2577 Kongzili",
        "jenis": "libur_nasional",
        "hari": "Selasa"
      },
      {
        "tanggal": "2026-03-19",
        "keterangan": "Hari Suci Nyepi (Tahun Baru Saka 1948)",
        "jenis": "libur_nasional",
        "hari": "Kamis"
      },
      {
        "tanggal": "2026-03-21",
        "keterangan": "Idulfitri 1447 H (hari pertama)",
        "jenis": "libur_nasional",
        "hari": "Sabtu"
      }
    ]
  }
}
```

### GET /libur/cek/{tanggal}

**Cek satu tanggal libur atau bukan**

- URL: `GET https://api.apipedia.id/v1/libur/cek/{tanggal}`
- Auth: `Authorization: Bearer <token>`
- operationId: `checkHolidayDate`

Memeriksa status satu tanggal. Mengembalikan keterangan libur bila tanggal tersebut libur nasional atau cuti bersama, plus penanda akhir pekan. Bila bukan libur, pesan berbunyi "Tanggal X bukan hari libur nasional" dan `jenis`/`keterangan` bernilai null.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `tanggal` | path | string | ya | Format YYYY-MM-DD. contoh: 2026-08-17 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/libur/cek/2026-08-17" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tanggal 2026-08-17 adalah hari libur nasional",
  "data": {
    "tanggal": "2026-08-17",
    "hari": "Senin",
    "is_libur": true,
    "is_akhir_pekan": false,
    "jenis": "libur_nasional",
    "keterangan": "Proklamasi Kemerdekaan Republik Indonesia"
  }
}
```

### GET /libur/berikutnya

**Hari libur nasional terdekat**

- URL: `GET https://api.apipedia.id/v1/libur/berikutnya`
- Auth: `Authorization: Bearer <token>`
- operationId: `getNextHoliday`

Libur nasional (bukan cuti bersama) pertama setelah tanggal `dari`. `sisa_hari` adalah jarak dalam hari dari `dari`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `dari` | query | string | tidak | Titik awal pencarian, format YYYY-MM-DD. Default hari ini (WIB). contoh: 2026-09-13 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/libur/berikutnya?dari=2026-09-13" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hari libur berikutnya berhasil diambil",
  "data": {
    "tanggal": "2026-12-25",
    "keterangan": "Kelahiran Yesus Kristus (Natal)",
    "jenis": "libur_nasional",
    "hari": "Jumat",
    "sisa_hari": 103
  }
}
```

### GET /libur/hari-kerja

**Hitung jumlah hari kerja antara dua tanggal**

- URL: `GET https://api.apipedia.id/v1/libur/hari-kerja`
- Auth: `Authorization: Bearer <token>`
- operationId: `countWorkingDays`

Menghitung hari kerja efektif (inklusif kedua ujung) dengan mengecualikan akhir pekan, libur nasional, dan cuti bersama. Berguna untuk kalkulasi SLA dan estimasi pengiriman. `sampai` tidak boleh lebih awal dari `mulai`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `mulai` | query | string | ya | Format YYYY-MM-DD. contoh: 2026-09-01 |
| `sampai` | query | string | ya | Format YYYY-MM-DD. contoh: 2026-09-30 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/libur/hari-kerja?mulai=2026-09-01&sampai=2026-09-30" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Jumlah hari kerja berhasil dihitung",
  "data": {
    "mulai": "2026-09-01",
    "sampai": "2026-09-30",
    "total_hari": 30,
    "hari_kerja": 22,
    "akhir_pekan": 8,
    "libur": 0
  }
}
```

## Wilayah

Data Wilayah Indonesia (Kemendagri)

### GET /wilayah/provinsi

**Daftar seluruh provinsi**

- URL: `GET https://api.apipedia.id/v1/wilayah/provinsi`
- Auth: `Authorization: Bearer <token>`
- operationId: `listProvinsi`

Mengembalikan 38 provinsi beserta kode Kemendagri, ibu kota, koordinat pusat, dan zona waktu (WIB/WITA/WIT).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wilayah/provinsi" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar provinsi berhasil diambil",
  "data": {
    "jumlah": 38,
    "provinsi": [
      {
        "kode": "11",
        "nama": "Aceh",
        "ibukota": "Banda Aceh",
        "lat": 5.57055,
        "lng": 95.34081,
        "tz": "WIB"
      },
      {
        "kode": "32",
        "nama": "Jawa Barat",
        "ibukota": "Bandung",
        "lat": -6.90222,
        "lng": 107.61876,
        "tz": "WIB"
      }
    ]
  }
}
```

### GET /wilayah/kabupaten/{kodeProvinsi}

**Daftar kabupaten dan kota dalam satu provinsi**

- URL: `GET https://api.apipedia.id/v1/wilayah/kabupaten/{kodeProvinsi}`
- Auth: `Authorization: Bearer <token>`
- operationId: `listKabupaten`

Kode provinsi yang tidak dikenal dijawab 400 (bukan 404) dengan contoh kode yang benar.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kodeProvinsi` | path | string | ya | Kode provinsi Kemendagri dua digit. contoh: 32 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wilayah/kabupaten/32" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar kabupaten dan kota provinsi Jawa Barat berhasil diambil",
  "data": {
    "kodeProvinsi": "32",
    "provinsi": "Jawa Barat",
    "jumlah": 27,
    "kabupaten": [
      {
        "kode": "32.73",
        "nama": "Kota Bandung",
        "ibukota": "Bandung",
        "lat": -6.91066,
        "lng": 107.60987,
        "elevasi": 726
      }
    ]
  }
}
```

### GET /wilayah/kecamatan/{kodeKabupaten}

**Daftar kecamatan dalam satu kabupaten atau kota**

- URL: `GET https://api.apipedia.id/v1/wilayah/kecamatan/{kodeKabupaten}`
- Auth: `Authorization: Bearer <token>`
- operationId: `listKecamatan`

Kode kabupaten memakai format bertitik (contoh 32.73). Kode tidak dikenal dijawab 400.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kodeKabupaten` | path | string | ya | Kode kabupaten/kota bertitik, contoh 32.73. contoh: 32.73 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wilayah/kecamatan/32.73" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar kecamatan Kota Bandung berhasil diambil",
  "data": {
    "kodeKabupaten": "32.73",
    "kabupaten": "Kota Bandung",
    "jumlah": 30,
    "kecamatan": [
      {
        "kode": "32.73.01",
        "nama": "Sukasari",
        "kodepos": [
          "40151",
          "40152",
          "40153",
          "40154"
        ]
      }
    ]
  }
}
```

### GET /wilayah/kodepos/{kodepos}

**Cari wilayah berdasarkan kode pos**

- URL: `GET https://api.apipedia.id/v1/wilayah/kodepos/{kodepos}`
- Auth: `Authorization: Bearer <token>`
- operationId: `lookupKodePos`

Kode pos harus 5 digit. Kode pos yang tidak ada pada data dijawab 400 (berbeda dari /kodepos/{kodepos} yang menjawab 404).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kodepos` | path | string | ya | Kode pos 5 digit. contoh: 40151 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wilayah/kodepos/40151" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Wilayah dengan kode pos 40151 berhasil ditemukan",
  "data": {
    "kodepos": "40151",
    "jumlah": 1,
    "hasil": [
      {
        "kecamatan": {
          "kode": "32.73.01",
          "nama": "Sukasari"
        },
        "kabupaten": {
          "kode": "32.73",
          "nama": "Kota Bandung"
        },
        "provinsi": {
          "kode": "32",
          "nama": "Jawa Barat"
        }
      }
    ]
  }
}
```

## Identitas

Parsing dan validasi NIK

### GET /nik/parse/{nik}

**Uraikan satu NIK menjadi data terstruktur**

- URL: `GET https://api.apipedia.id/v1/nik/parse/{nik}`
- Auth: `Authorization: Bearer <token>`
- operationId: `parseNik`

Memeriksa struktur NIK sesuai PP 37/2007 lalu menguraikannya: wilayah penerbitan, jenis kelamin (tanggal +40 untuk perempuan), tanggal lahir, dan nomor urut. Pemeriksaan bersifat struktural, bukan verifikasi identitas ke Dukcapil. NIK yang tidak valid dijawab 400 dengan alasan pertama (panjang bukan 16 digit, bukan angka, kode wilayah tidak terdaftar, digit tanggal tidak valid). `abad_ambigu: true` berarti dua digit tahun bisa berarti 19xx atau 20xx; tahun alternatifnya ada di `tanggal_lahir_alternatif`. Hasil tidak di-cache.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `nik` | path | string | ya | NIK 16 digit. contoh: 3273010101900001 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/nik/parse/3273010101900001" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "NIK berhasil diuraikan",
  "data": {
    "valid": true,
    "nik": "3273010101900001",
    "wilayah": {
      "kode_provinsi": "32",
      "provinsi": "Jawa Barat",
      "kode_kabupaten": "32.73",
      "kabupaten": "Kota Bandung",
      "kode_kecamatan": "32.73.01",
      "kecamatan": "Sukasari",
      "kode_historis": false
    },
    "jenis_kelamin": "laki-laki",
    "tanggal_lahir": "1990-01-01",
    "tanggal_lahir_alternatif": null,
    "abad_ambigu": false,
    "nomor_urut": "0001",
    "catatan": [
      "Pemeriksaan ini bersifat struktural. NIK tidak memiliki checksum, sehingga struktur yang benar bukan bukti identitas terdaftar.",
      "Kode wilayah menunjukkan tempat tinggal saat NIK diterbitkan, bukan tempat lahir dan bukan domisili terkini."
    ]
  }
}
```

## Tanggal

Konversi dan perhitungan tanggal

### GET /tanggal/hari-ini

**Tanggal hari ini dalam beberapa kalender**

- URL: `GET https://api.apipedia.id/v1/tanggal/hari-ini`
- Auth: `Authorization: Bearer <token>`
- operationId: `getToday`

Tanggal hari ini menurut zona WIB: Masehi, nama hari, pasaran Jawa, Hijriah, dan status libur nasional.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tanggal/hari-ini" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tanggal hari ini berhasil diambil",
  "data": {
    "masehi": "2026-09-13",
    "hari": "Minggu",
    "pasaran": "Pon",
    "hijriah": "30 Rabiulawal 1448 H",
    "is_libur": false,
    "keterangan_libur": null
  }
}
```

### GET /tanggal/selisih

**Hitung selisih dua tanggal**

- URL: `GET https://api.apipedia.id/v1/tanggal/selisih`
- Auth: `Authorization: Bearer <token>`
- operationId: `diffDates`

Selisih hari, bulan penuh, dan hari kerja (Senin-Jumat dikurangi libur nasional) di antara dua tanggal. Kalau `sampai` lebih awal dari `mulai`, hasilnya negatif.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `mulai` | query | string | ya | Tanggal awal, format YYYY-MM-DD. contoh: 2026-09-01 |
| `sampai` | query | string | ya | Tanggal akhir, format YYYY-MM-DD. contoh: 2026-12-25 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tanggal/selisih?mulai=2026-09-01&sampai=2026-12-25" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Selisih tanggal berhasil dihitung",
  "data": {
    "mulai": "2026-09-01",
    "sampai": "2026-12-25",
    "hari": 115,
    "bulan": 3,
    "hari_kerja": 82
  }
}
```

## Sholat

Jadwal sholat Indonesia

### GET /sholat/harian

**Jadwal sholat satu hari**

- URL: `GET https://api.apipedia.id/v1/sholat/harian`
- Auth: `Authorization: Bearer <token>`
- operationId: `getJadwalSholatHarian`

Dihitung dengan kriteria Kemenag (Subuh 20 derajat, Isya 18 derajat di bawah ufuk) plus ihtiyath 2 menit, memakai koordinat dan elevasi kabupaten/kota. Jam dalam zona waktu lokal kota itu (`zona_waktu`).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kota` | query | string | ya | Kode kabupaten/kota Kemendagri (contoh 32.73) atau namanya. contoh: 32.73 |
| `tanggal` | query | string | tidak | Format YYYY-MM-DD. Default hari ini (WIB). contoh: 2026-09-13 |
| `madhab` | query | string | tidak | Mazhab untuk waktu Ashar. pilihan: syafii, hanafi; default: syafii; contoh: syafii |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/sholat/harian?kota=32.73&tanggal=2026-09-13&madhab=syafii" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Jadwal sholat berhasil dihitung",
  "data": {
    "kota": "Kota Bandung",
    "kode_kota": "32.73",
    "zona_waktu": "WIB",
    "koordinat": {
      "lat": -6.91066,
      "lng": 107.60987,
      "elevasi": 726
    },
    "madhab": "syafii",
    "tanggal": "2026-09-13",
    "imsak": "04:20",
    "subuh": "04:30",
    "terbit": "05:39",
    "dzuhur": "11:48",
    "ashar": "15:04",
    "maghrib": "17:53",
    "isya": "18:59"
  }
}
```

## OCR

Pembacaan teks dan dokumen identitas dengan Tesseract (ind+eng)

### POST /ocr/general

**Ekstraksi teks dari gambar**

- URL: `POST https://api.apipedia.id/v1/ocr/general`
- Auth: `Authorization: Bearer <token>`
- operationId: `ocrGeneral`

Membaca teks dari gambar memakai mesin OCR Tesseract (ind+eng) yang berjalan sebagai WebAssembly di server. Masukan berupa gambar PNG, JPEG, atau WebP dalam base64 (boleh dalam bentuk data URL), maksimal 8 MB; PDF belum didukung.
**Mode.** Field opsional `mode`: `default` (bawaan, Tesseract seperti di atas) atau `ai`. Mode `ai` mengirim gambar ke model vision lewat AI Gateway dan **memotong kredit AI** Anda (model gratis memakai kuota harian model gratis); field `model` wajib berisi public id model yang mendukung gambar dari GET /ai/models. API key yang dipakai harus punya cakupan `ai-gateway` (atau berlaku untuk seluruh gateway). Mode `ai` tidak pernah dilayani dari cache. Bentuk `data` sama dengan mode default (`confidence` dan `lang` bernilai null), ditambah `mode`, `model`, dan `usage` (pemakaian token dari gateway).
Contoh mode `ai`:
```json
{ "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", "mode": "ai", "model": "gpt-4o-mini" }
```
Respons `data`:
```json
{ "text": "NIK 3273012501990001\nNama BUDI SANTOSO", "confidence": null, "lang": null, "mode": "ai", "model": "gpt-4o-mini", "usage": { "prompt_tokens": 912, "completion_tokens": 24, "total_tokens": 936 } }
```
Error: 400 `image_base64` kosong/bukan base64/lebih dari 8 MB/bukan PNG-JPEG-WebP, `mode` selain `default`/`ai`, atau `model` kosong pada mode `ai`; 502 `Mesin OCR gagal memproses gambar yang dikirim.` (default) atau jawaban model yang bukan JSON (ai). Error AI Gateway pada mode `ai` diteruskan dengan status yang sama (402, 403, 404, 429).

Body JSON:

```json
{
  "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ocr/general" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Teks berhasil diekstraksi dari gambar",
  "data": {
    "text": "NIK 3273012501990001",
    "confidence": 91.4,
    "lang": "ind+eng"
  }
}
```

### POST /ocr/ktp

**Ekstraksi field KTP dari foto**

- URL: `POST https://api.apipedia.id/v1/ocr/ktp`
- Auth: `Authorization: Bearer <token>`
- operationId: `ocrKtp`

Menjalankan OCR Tesseract (ind+eng) pada foto KTP lalu menguraikan field-nya lewat pencocokan pola. Field yang tidak terbaca bernilai null, dan teks mentah (`raw_text`) selalu disertakan supaya hasilnya bisa diperiksa ulang. Kalau NIK terbaca, `nik_parsed` berisi hasil penguraian struktur NIK (kunci camelCase; null kalau NIK tidak terbaca). Body: `image_base64` (PNG/JPEG/WebP, maks 8 MB). Verifikasi manual tetap diperlukan.
**Mode.** Field opsional `mode`: `default` (bawaan, Tesseract seperti di atas) atau `ai`. Mode `ai` mengirim foto ke model vision lewat AI Gateway dan **memotong kredit AI** Anda; field `model` wajib berisi public id model yang mendukung gambar dari GET /ai/models, dan API key harus punya cakupan `ai-gateway` (atau berlaku untuk seluruh gateway). Model diminta mengisi field yang sama persis dengan mode default dan memberi null bila tidak terbaca; nilainya dinormalkan ke format yang sama (NIK 16 digit, tanggal dd-mm-yyyy, `LAKI-LAKI`/`PEREMPUAN`, RT/RW `005/012`) dan NIK diuraikan ulang menjadi `nik_parsed`. Kunci `data` sama dengan mode default (`confidence` dan `lang` null, `raw_text` berisi salinan teks dari model), ditambah `mode`, `model`, dan `usage`. Mode `ai` tidak pernah dilayani dari cache. Jawaban model yang bukan JSON dijawab 502 tanpa data.
Contoh mode `ai`:
```json
{ "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", "mode": "ai", "model": "gpt-4o-mini" }
```
Respons `data` (dipotong):
```json
{ "nik": "3273012501990001", "nama": "BUDI SANTOSO", "tempat_lahir": "BANDUNG", "tanggal_lahir": "25-01-1999", "jenis_kelamin": "LAKI-LAKI", "...": "field lain sama dengan mode default", "nik_parsed": { "valid": true, "...": "..." }, "confidence": null, "lang": null, "raw_text": "PROVINSI JAWA BARAT\nKOTA BANDUNG\nNIK : 3273012501990001", "catatan": "Akurasi bergantung kualitas foto; verifikasi manual tetap diperlukan.", "mode": "ai", "model": "gpt-4o-mini", "usage": { "prompt_tokens": 1104, "completion_tokens": 286, "total_tokens": 1390 } }
```
Error: 400 gambar tidak sah, `mode` selain `default`/`ai`, atau `model` kosong pada mode `ai`; 402/403/404/429 dari AI Gateway diteruskan pada mode `ai`; 502 mesin OCR gagal (default) atau jawaban model bukan JSON (ai).

Body JSON:

```json
{
  "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ocr/ktp" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Field KTP berhasil dibaca dari gambar",
  "data": {
    "nik": "3273012501990001",
    "nama": "BUDI SANTOSO",
    "tempat_lahir": "BANDUNG",
    "tanggal_lahir": "25-01-1999",
    "jenis_kelamin": "LAKI-LAKI",
    "alamat": "JL. SETIABUDI NO. 123",
    "rt_rw": "005/012",
    "kel_desa": "GEGERKALONG",
    "kecamatan": "SUKASARI",
    "agama": "ISLAM",
    "status_perkawinan": "BELUM KAWIN",
    "pekerjaan": "KARYAWAN SWASTA",
    "kewarganegaraan": "WNI",
    "berlaku_hingga": "SEUMUR HIDUP",
    "nik_parsed": {
      "valid": true,
      "nik": "3273012501990001",
      "errors": [],
      "catatan": [
        "Pemeriksaan ini bersifat struktural. NIK tidak memiliki checksum, sehingga struktur yang benar bukan bukti identitas terdaftar."
      ],
      "wilayah": {
        "kodeProvinsi": "32",
        "provinsi": "Jawa Barat",
        "kodeKabupaten": "32.73",
        "kabupaten": "Kota Bandung",
        "kodeKecamatan": "32.73.01",
        "kecamatan": "Sukasari",
        "kodeHistoris": false
      },
      "jenisKelamin": "laki-laki",
      "tanggalLahir": "1999-01-25",
      "abadAmbigu": false,
      "nomorUrut": "0001"
    },
    "confidence": 88.2,
    "lang": "ind+eng",
    "raw_text": "PROVINSI JAWA BARAT / KOTA BANDUNG / NIK : 3273012501990001",
    "catatan": "Akurasi bergantung kualitas foto; verifikasi manual tetap diperlukan."
  }
}
```

### POST /ocr/sim

**Ekstraksi field SIM dari foto**

- URL: `POST https://api.apipedia.id/v1/ocr/sim`
- Auth: `Authorization: Bearer <token>`
- operationId: `ocrSim`

Menjalankan OCR Tesseract (ind+eng) pada foto SIM lalu menguraikan nomor, nama, alamat, golongan, dan masa berlaku lewat pencocokan pola. Field yang tidak terbaca bernilai null dan teks mentah selalu disertakan.
**Mode.** Field opsional `mode`: `default` (bawaan, Tesseract seperti di atas) atau `ai`. Mode `ai` mengirim foto ke model vision lewat AI Gateway dan **memotong kredit AI** Anda; field `model` wajib berisi public id model yang mendukung gambar dari GET /ai/models, dan API key harus punya cakupan `ai-gateway` (atau berlaku untuk seluruh gateway). Kunci `data` sama dengan mode default (`confidence` dan `lang` null, `raw_text` berisi salinan teks dari model), ditambah `mode`, `model`, dan `usage`; nilai yang tidak terbaca null, tanggal dinormalkan ke dd-mm-yyyy. Mode `ai` tidak pernah dilayani dari cache. Jawaban model yang bukan JSON dijawab 502 tanpa data.
Contoh mode `ai`:
```json
{ "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", "mode": "ai", "model": "gpt-4o-mini" }
```
Respons `data`:
```json
{ "nomor_sim": "1234-5678-901234", "nama": "BUDI SANTOSO", "alamat": "JL. SETIABUDI NO. 123", "golongan": "A", "berlaku_hingga": "25-01-2029", "confidence": null, "lang": null, "raw_text": "SURAT IZIN MENGEMUDI\n1234-5678-901234\nBUDI SANTOSO", "catatan": "Akurasi bergantung kualitas foto; verifikasi manual tetap diperlukan.", "mode": "ai", "model": "gpt-4o-mini", "usage": { "prompt_tokens": 1080, "completion_tokens": 140, "total_tokens": 1220 } }
```
Error: 400 gambar tidak sah, `mode` selain `default`/`ai`, atau `model` kosong pada mode `ai`; 402/403/404/429 dari AI Gateway diteruskan pada mode `ai`; 502 mesin OCR gagal (default) atau jawaban model bukan JSON (ai).

Body JSON:

```json
{
  "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/ocr/sim" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAggAAAB4AQAAAAC/OjcmAAAEc0lEQVR42u2YsY5cNRSGP99xshbZwgWKtogyLimQSINEAayFEKJMSbk1T5BisuvMrtCWlJRLl4eIFM8qRQqKfQLwQAqgwRMFyRt5bArPrDbJ3AnSFiCY24zuvTOfjv9zfHz+EZUrXh0bwoawIWwI/3VCx6RDTQUPxFzsVV0lzA7UuRU7VMkENRV2ps/tXPUQKlSXwQI8JFEAnwn8zvO5w2Xg7DmBvJpQAbyEAPASVQAsGKqLnIBc3Od1OgQgvvKmXDz3b1fSiUgmMagMq0vU9rOw5U98ipCpj7cCYUF9g1CgbkfJN6lA9KgMYCsm30HIRJA4uIVJfTFoBBAcHWgDGKDgqlSMO3wkOFTHQus3CRkPZNMeaJ8SDk5hioyIbMkGEoRYe2J4j5q1ZF8ViBTFXcCqpfYCiRMZDCe9uRCpwoiDDq0uqtQjuyqBBA/c3PTnIhGqFhmFq5BIEQ9ehkARAKMMnmeEgFsdw+d4jGyKRoXiGLAdJnc1A1aC9QUjdN8qosUSMJB1Kq2OCrHKIiRQCHCnle6bBNlKcXBwZISXZQ5dCjssckHNANlUgOA/6InhCJGFpLAPERQKsCKZjADJUOLAJ1N7VhFIFaBVVLT4tkMcMmsqXoMDc4xrm29lNiEvdA4lLXJJILFfsGSo4ML6HtUky9GOWxuxWJPPTB0plMShTzB9hEnNj9u+qELOgMOS2n6bW+yxBsIDO3PUrRxX5gLcp5BNW8WAhABOMVPwU0Bmg0AQVnbVDjoKVAk4QBYlyYseJdhhzyOh0mEUaSXBQUWAwzHiq265ATXc5PrA2Q6Yhlsr+wuIzUy7IWwIG8KGsCH83winVyJMX5vR5uLyqPGbZsKfiu9tmxRWxuAuRu6j9vHr8q2C78CaH6h7zOduDeHVOfGyl7FUommTll+jg11hb5bTdYFAhQR/N4bh5Zt34XZ6B+7CvfB2HdoVH1+6+Zos8oeIh0QZ+whzgZ99IphO72thQTM90DM9k+fnhgJmsQa3ZhXGRosJJ8BTAPY5e8H8aPkFAaqv+jpgRPRhRDQOTYY40WN8gUeYpmq/jo1wrWAn1wpBHP504EC7WBj8PEhbW56OBAIUoZ8wJvlxRzIfI1yBKYw52tkmAAVFqZAw9LpF17Er6QifwcQBHfuMZPPpHYlxi6Go3lyA35mDWaZV6jFz/2JRXUv/ZdYp2RxAWLh4PooOvowsY3irDm6IjwwxwJaHoWefvZeMXouh9BLSEJvSkABFeYgw5jZhdGGCWj2sqSjLJC/stgY0uiC8uNhlclEPq2wWEjj+AouCMCyL/yAieGEgQ4f8o8WgH/Wv4kf8bioY6JKDiH7EmQj3AE5n7jmTc6tOFwW/inD8DGuOnxDYNect2KfMrPmW+xZPhzJUUKuNVgfhaIDXTwYYEK1TqOvQ6e3WewR3QjOefT3KDG5g1S83CBgOPUTkTfZubb+Pba7RGyoMDvs8zmT3queFv9qJI+r8WvnnT73rVyV0O1fVYTOBbAgbwoawIfzbCX8BhxDkVglmJ4gAAAAASUVORK5CYII="}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Field SIM berhasil dibaca dari gambar",
  "data": {
    "nomor_sim": "1234-5678-901234",
    "nama": "BUDI SANTOSO",
    "alamat": "JL. SETIABUDI NO. 123",
    "golongan": "A",
    "berlaku_hingga": "25-01-2029",
    "confidence": 86.5,
    "lang": "ind+eng",
    "raw_text": "SURAT IZIN MENGEMUDI / 1234-5678-901234",
    "catatan": "Akurasi bergantung kualitas foto; verifikasi manual tetap diperlukan."
  }
}
```

## Laporan

Laporan masalah lewat API. Setiap laporan menjadi tiket di menu Tiket konsol

### GET /report

**Daftar laporan milik akun ini**

- URL: `GET https://api.apipedia.id/v1/report`
- Auth: `Authorization: Bearer <token>`
- operationId: `listReports`

Maksimal 50 laporan terbaru yang dikirim lewat Report API, terbaru lebih dulu.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/report" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar laporan berhasil diambil",
  "data": {
    "items": [
      {
        "id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f4b2a10",
        "ticket_number": "202610290007",
        "category": "bug",
        "subject": "Respons /qr/generate lambat",
        "status": "ditinjau",
        "created_at": "2026-10-29T04:20:00Z",
        "updated_at": "2026-10-29T06:05:00Z",
        "resolved_at": null,
        "console_url": "https://console.apipedia.id/tickets"
      }
    ],
    "total": 1
  }
}
```

### POST /report

**Kirim laporan masalah atau masukan ke tim APIPedia**

- URL: `POST https://api.apipedia.id/v1/report`
- Auth: `Authorization: Bearer <token>`
- operationId: `createReport`

Kategori yang didukung: data_salah, bug, dokumentasi, penyalahgunaan, saran_fitur, dan lainnya (bawaan). Hanya `description` (minimal 10 karakter) yang wajib; `subject` diambil dari awal deskripsi kalau kosong. Field `endpoint` dan `request_id` opsional, isi kalau laporan terkait satu pemanggilan API tertentu supaya tim bisa menelusuri lebih cepat. Laporan disimpan sebagai tiket dan langsung muncul di menu Tiket konsol; balasan tim juga bisa dibaca di sana. Laporan penyalahgunaan otomatis berprioritas tinggi.

Body JSON:

```json
{
  "category": "bug",
  "subject": "Respons /qr/generate lambat",
  "description": "Endpoint QR butuh lebih dari 10 detik sejak pagi ini.",
  "endpoint": "/qr/generate",
  "request_id": "req_8a12f0"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/report" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"category":"bug","subject":"Respons /qr/generate lambat","description":"Endpoint QR butuh lebih dari 10 detik sejak pagi ini.","endpoint":"/qr/generate","request_id":"req_8a12f0"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Laporan berhasil dikirim. Pantau statusnya di menu Tiket konsol.",
  "data": {
    "id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f4b2a10",
    "ticket_number": "202610290007",
    "category": "bug",
    "subject": "Respons /qr/generate lambat",
    "status": "diterima",
    "created_at": "2026-10-29T04:20:00Z",
    "updated_at": "2026-10-29T04:20:00Z",
    "resolved_at": null,
    "console_url": "https://console.apipedia.id/tickets"
  }
}
```

### GET /report/{id}

**Cek status satu laporan**

- URL: `GET https://api.apipedia.id/v1/report/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getReport`

`id` boleh berupa id laporan (uuid) atau nomor tiket 12 digit yang tampil di konsol. Status: diterima, ditinjau, menunggu_balasan, selesai, ditutup. `last_reply` berisi balasan terakhir dari tim, atau null kalau belum ada.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: 202610290007 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/report/202610290007" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status laporan berhasil diambil",
  "data": {
    "id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f4b2a10",
    "ticket_number": "202610290007",
    "category": "bug",
    "subject": "Respons /qr/generate lambat",
    "status": "menunggu_balasan",
    "created_at": "2026-10-29T04:20:00Z",
    "updated_at": "2026-10-29T06:05:00Z",
    "resolved_at": null,
    "last_reply": {
      "message": "Terima kasih, sudah kami perbaiki. Bisa dicoba lagi?",
      "created_at": "2026-10-29T06:05:00Z"
    },
    "console_url": "https://console.apipedia.id/tickets"
  }
}
```

## Koneksi

Koneksi akun pihak ketiga (Gmail, Google, LinkedIn, GitHub) lewat OAuth Composio. Wajib aktif sebelum memanggil /toolkits/*

### GET /connections

**Daftar semua koneksi akun pihak ketiga**

- URL: `GET https://api.apipedia.id/v1/connections`
- Auth: `Authorization: Bearer <token>`
- operationId: `listConnections`

Mengembalikan baris koneksi milik pemanggil untuk toolkit yang pernah disentuh (gmail, googlecalendar, googlemeet, googledocs, googlesheets, googledrive, linkedin, github). Toolkit yang belum pernah dihubungkan tidak muncul sama sekali, anggap statusnya `disconnected`. Baris yang masih `pending`, atau `active` tetapi belum berlabel akun, disinkronkan ulang ke Composio sebelum dikembalikan. Status yang mungkin: disconnected, pending, active, expired, failed.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/connections" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar koneksi berhasil diambil",
  "data": {
    "items": [
      {
        "id": "5b0f8a52-6c1d-4f7e-9a3b-2d4e6f8a0b1c",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "toolkit": "gmail",
        "status": "active",
        "composio_account_id": "ca_8Kq2mN4pR6sT",
        "account_label": "budi@contoh.com",
        "app_origin": "https://console.apipedia.id",
        "connected_at": "2026-09-01T02:00:00Z",
        "created_at": "2026-09-01T01:58:12Z",
        "updated_at": "2026-09-01T02:00:00Z"
      },
      {
        "id": "7e1a9c34-2b5d-4a6f-8c0e-1f3a5b7d9e2c",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "toolkit": "github",
        "status": "pending",
        "composio_account_id": "ca_3Hj5kL7mN9pQ",
        "account_label": null,
        "app_origin": "https://console.apipedia.id",
        "connected_at": null,
        "created_at": "2026-09-13T04:20:00Z",
        "updated_at": "2026-09-13T04:20:00Z"
      }
    ]
  }
}
```

### POST /connections/{toolkit}/initiate

**Mulai proses hubungkan akun pihak ketiga**

- URL: `POST https://api.apipedia.id/v1/connections/{toolkit}/initiate`
- Auth: `Authorization: Bearer <token>`
- operationId: `initiateConnection`

Memulai alur OAuth untuk satu toolkit. APIPedia meminta URL otorisasi dari Composio lalu mengembalikan `redirectUrl` yang harus dibuka pengguna di browser untuk login dan memberi izin di sisi penyedia. Setelah selesai, penyedia mengarahkan balik ke `<app_origin>/connections?toolkit=<toolkit>&status=<status>`. Body opsional `app_origin` (http/https) menentukan ke mana pengguna dikembalikan.
Error: 404 toolkit tidak dikenal, 400 untuk toolkit perangkat (whatsapp dipasangkan lewat QR, bukan OAuth) atau bila Composio menolak, 503 bila COMPOSIO_API_KEY server kosong.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `toolkit` | path | string | ya | gmail, googlecalendar, googlemeet, googledocs, googlesheets, googledrive, linkedin, atau github. pilihan: gmail, googlecalendar, googlemeet, googledocs, googlesheets, googledrive, linkedin, github; contoh: gmail |

Body JSON (opsional):

```json
{
  "app_origin": "https://console.apipedia.id"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/connections/gmail/initiate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"app_origin":"https://console.apipedia.id"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tautan otorisasi berhasil dibuat",
  "data": {
    "redirectUrl": "https://backend.composio.dev/api/v3/s/Xk2mP9qR",
    "connection": {
      "id": "5b0f8a52-6c1d-4f7e-9a3b-2d4e6f8a0b1c",
      "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "toolkit": "gmail",
      "status": "pending",
      "composio_account_id": "ca_8Kq2mN4pR6sT",
      "account_label": null,
      "app_origin": "https://console.apipedia.id",
      "connected_at": null,
      "created_at": "2026-09-13T04:20:00Z",
      "updated_at": "2026-09-13T04:20:00Z"
    }
  }
}
```

### GET /connections/{toolkit}/status

**Cek status koneksi satu toolkit**

- URL: `GET https://api.apipedia.id/v1/connections/{toolkit}/status`
- Auth: `Authorization: Bearer <token>`
- operationId: `getConnectionStatus`

Membaca baris koneksi satu toolkit. Kalau belum pernah dihubungkan, `status` bernilai `disconnected` dan `connection` bernilai null. Pakai endpoint ini untuk polling setelah pengguna membuka `redirectUrl` dari /initiate, sampai status menjadi `active`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `toolkit` | path | string | ya | contoh: gmail |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/connections/gmail/status" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status koneksi berhasil diambil",
  "data": {
    "toolkit": "gmail",
    "status": "active",
    "connection": {
      "id": "5b0f8a52-6c1d-4f7e-9a3b-2d4e6f8a0b1c",
      "toolkit": "gmail",
      "status": "active",
      "account_label": "budi@contoh.com",
      "connected_at": "2026-09-01T02:00:00Z",
      "updated_at": "2026-09-01T02:00:00Z"
    }
  }
}
```

### POST /connections/{id}/resume

**Lanjutkan otorisasi akun yang masih menunggu**

- URL: `POST https://api.apipedia.id/v1/connections/{id}/resume`
- Auth: `Authorization: Bearer <token>`
- operationId: `resumeConnection`

Untuk akun berstatus `pending` (Menunggu otorisasi) yang halaman izinnya sempat ditutup. Mengembalikan `redirectUrl` untuk akun YANG SAMA: kalau tautan otorisasi di Composio masih baru (< 10 menit) tautan itu dipakai ulang (`reused: true`); kalau tidak, otorisasi dimulai ulang untuk slot akun yang sama (id baris dan penanda default tetap, akun Composio lama dibersihkan). Kalau ternyata akun sudah aktif, `status` bernilai `active` dan `redirectUrl` null. Body opsional `app_origin` seperti /initiate.
Error: 404 id tidak ditemukan pada profil Anda; 409 akun sudah diputus (pakai /initiate); 503 COMPOSIO_API_KEY server kosong.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id akun (connections.id) dari GET /connections. contoh: 7e1a9c34-2b5d-4a6f-8c0e-1f3a5b7d9e2c |

Body JSON (opsional):

```json
{
  "app_origin": "https://console.apipedia.id"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/connections/7e1a9c34-2b5d-4a6f-8c0e-1f3a5b7d9e2c/resume" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"app_origin":"https://console.apipedia.id"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tautan otorisasi yang tertunda berhasil diambil",
  "data": {
    "status": "pending",
    "redirectUrl": "https://backend.composio.dev/api/v3/s/Xk2mP9qR",
    "reused": true,
    "connection": {
      "id": "7e1a9c34-2b5d-4a6f-8c0e-1f3a5b7d9e2c",
      "toolkit": "github",
      "status": "pending",
      "account_label": null,
      "is_default": true
    }
  }
}
```

### DELETE /connections/{toolkit}

**Putuskan koneksi satu toolkit**

- URL: `DELETE https://api.apipedia.id/v1/connections/{toolkit}`
- Auth: `Authorization: Bearer <token>`
- operationId: `deleteConnection`

Mencabut akun terhubung di Composio (bila ada) lalu menandai baris koneksi `disconnected`. Aman dipanggil walau toolkit belum pernah dihubungkan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `toolkit` | path | string | ya | contoh: gmail |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/connections/gmail" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Koneksi berhasil diputus",
  "data": {
    "connection": {
      "id": "5b0f8a52-6c1d-4f7e-9a3b-2d4e6f8a0b1c",
      "toolkit": "gmail",
      "status": "disconnected",
      "composio_account_id": null,
      "account_label": null,
      "updated_at": "2026-09-13T05:00:00Z"
    }
  }
}
```

### GET /connections/{toolkit}/callback

**Callback OAuth (internal, dipanggil penyedia)**

- URL: `GET https://api.apipedia.id/v1/connections/{toolkit}/callback`
- **INTERNAL** - dipanggil pihak ketiga, bukan oleh agent
- Auth: tidak perlu token
- operationId: `connectionCallback`

INTERNAL. Tujuan redirect dari Composio setelah pengguna memberi izin; tidak memakai Bearer token dan tidak untuk dipanggil langsung. Status disinkronkan ulang ke Composio (nilai `status` di query tidak pernah dipercaya), lalu browser diarahkan dengan HTTP 302 ke `<app_origin>/connections?toolkit=<toolkit>&status=<status>`. Tidak mengembalikan amplop JSON.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `toolkit` | path | string | ya | contoh: gmail |
| `composio_account_id` | query | string | tidak | Id akun Composio (alias connectedAccountId). contoh: ca_8Kq2mN4pR6sT |

Contoh respons:

```json
{
  "is_success": true,
  "message": "302 Found ke halaman /connections",
  "data": null
}
```

## Downloader

Metadata dan tautan unduhan media publik (YouTube, Instagram, TikTok, Facebook, X, Threads) lewat yt-dlp

### POST /dl/{platform}/info

**Ambil metadata media dari platform pihak ketiga**

- URL: `POST https://api.apipedia.id/v1/dl/{platform}/info`
- Auth: `Authorization: Bearer <token>`
- operationId: `getDownloaderInfo`

Membaca metadata satu media publik lewat yt-dlp (judul, pengunggah, durasi, thumbnail, statistik). Host pada `url` harus cocok dengan platform di path: youtube.com/youtu.be, instagram.com, tiktok.com, facebook.com/fb.watch, x.com/twitter.com, atau threads.net/threads.com. Hanya konten publik; jangan dipakai untuk mengunduh konten yang tidak Anda miliki haknya.
Hanya postingan yang berisi VIDEO yang bisa diambil; postingan foto/teks dijawab 422 `data.reason: "no_video"`. Threads saat ini belum didukung mesin ekstraksi (yt-dlp) dan dijawab 501. Instagram kadang membatasi akses tanpa login dari IP server (503 `data.reason: "login_required"`).
Error: 404 platform tidak dikenal, 400 `url` kosong/tidak valid/host tidak cocok, 422 konten privat/dihapus/tanpa video, 501 jenis tautan belum didukung, 503 gagal mengambil data dari platform, platform tidak merespons dalam 45 detik, atau yt-dlp belum terpasang di server.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `platform` | path | string | ya | youtube, instagram, tiktok, facebook, x, atau threads. pilihan: youtube, instagram, tiktok, facebook, x, threads; contoh: youtube |

Body JSON:

```json
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/dl/youtube/info" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Metadata berhasil diambil",
  "data": {
    "platform": "youtube",
    "id": "dQw4w9WgXcQ",
    "title": "Contoh Video",
    "description": "Deskripsi video.",
    "uploader": "Kanal Contoh",
    "uploader_url": "https://www.youtube.com/@kanalcontoh",
    "duration_seconds": 213,
    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "view_count": 1520344,
    "like_count": 18233,
    "upload_date": "2026-08-14",
    "extractor": "Youtube",
    "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  }
}
```

### POST /dl/{platform}/fetch

**Ambil tautan unduhan media dari platform pihak ketiga**

- URL: `POST https://api.apipedia.id/v1/dl/{platform}/fetch`
- Auth: `Authorization: Bearer <token>`
- operationId: `fetchDownloaderMedia`

Sama seperti /info, ditambah `formats`: daftar format yang tersedia, diurutkan dari resolusi tertinggi. Tiap format membawa `url` langsung ke CDN platform. Tautan itu KEDALUWARSA dalam hitungan jam (`formats_expire: true`), jadi unduh segera dan jangan disimpan sebagai tautan permanen. Error sama dengan /info.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `platform` | path | string | ya | youtube, instagram, tiktok, facebook, x, atau threads. pilihan: youtube, instagram, tiktok, facebook, x, threads; contoh: youtube |

Body JSON:

```json
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/dl/youtube/fetch" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tautan unduhan berhasil diambil",
  "data": {
    "platform": "youtube",
    "id": "dQw4w9WgXcQ",
    "title": "Contoh Video",
    "uploader": "Kanal Contoh",
    "duration_seconds": 213,
    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "upload_date": "2026-08-14",
    "extractor": "Youtube",
    "webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "formats": [
      {
        "format_id": "18",
        "ext": "mp4",
        "quality_label": "360p",
        "width": 640,
        "height": 360,
        "fps": 25,
        "filesize_bytes": 9123456,
        "has_video": true,
        "has_audio": true,
        "url": "https://rr3---sn-example.googlevideo.com/videoplayback?expire=1760000000"
      },
      {
        "format_id": "140",
        "ext": "m4a",
        "quality_label": "medium",
        "width": null,
        "height": null,
        "fps": null,
        "filesize_bytes": 3456789,
        "has_video": false,
        "has_audio": true,
        "url": "https://rr3---sn-example.googlevideo.com/videoplayback?expire=1760000000"
      }
    ],
    "formats_expire": true
  }
}
```

## Ongkir

Estimasi ongkos kirim dalam negeri dari data tarif statis

### GET /ongkir/couriers

**Daftar kurir dan layanan yang didukung**

- URL: `GET https://api.apipedia.id/v1/ongkir/couriers`
- Auth: `Authorization: Bearer <token>`
- operationId: `listOngkirCouriers`

Kurir ekspedisi (JNE/POS/TIKI) ditagih per kilogram, kurir instan (GoSend/GrabExpress/SPX/Maxim) per kilometer. Keduanya muncul di daftar yang sama, tetapi perhitungannya di /ongkir/cek terpisah.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ongkir/couriers" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar kurir berhasil diambil",
  "data": {
    "jumlah": 10,
    "kurir": [
      {
        "kode": "jne_yes",
        "nama": "JNE YES"
      },
      {
        "kode": "jne_reg",
        "nama": "JNE Reguler"
      },
      {
        "kode": "pos_express",
        "nama": "POS Express"
      },
      {
        "kode": "pos_kilat",
        "nama": "POS Kilat Khusus"
      },
      {
        "kode": "tiki_express",
        "nama": "TIKI Express"
      },
      {
        "kode": "tiki_reguler",
        "nama": "TIKI Reguler"
      },
      {
        "kode": "gosend_instant",
        "nama": "GoSend Instant"
      },
      {
        "kode": "grabexpress_instant",
        "nama": "GrabExpress Instant"
      },
      {
        "kode": "spx_instant",
        "nama": "Shopee Xpress Instant"
      },
      {
        "kode": "maxim_delivery",
        "nama": "Maxim Pengiriman"
      }
    ]
  }
}
```

### GET /ongkir/search

**Cari id kabupaten atau kecamatan**

- URL: `GET https://api.apipedia.id/v1/ongkir/search`
- Auth: `Authorization: Bearer <token>`
- operationId: `searchOngkirWilayah`

Mencari wilayah berdasarkan nama untuk mendapatkan id yang dipakai parameter origin dan destination pada /ongkir/cek. Minimal 2 karakter, maksimal 20 hasil.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama kabupaten atau kecamatan, minimal 2 karakter. contoh: bandung |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ongkir/search?q=bandung" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ditemukan 2 wilayah yang cocok",
  "data": {
    "q": "bandung",
    "jumlah": 2,
    "hasil": [
      {
        "tipe": "kabupaten",
        "id": "3273",
        "nama": "KOTA BANDUNG",
        "provinsi": "Jawa Barat"
      },
      {
        "tipe": "kabupaten",
        "id": "3204",
        "nama": "KAB. BANDUNG",
        "provinsi": "Jawa Barat"
      }
    ]
  }
}
```

### GET /ongkir/cek

**Hitung estimasi ongkos kirim**

- URL: `GET https://api.apipedia.id/v1/ongkir/cek`
- Auth: `Authorization: Bearer <token>`
- operationId: `cekOngkir`

Menghitung perkiraan ongkos kirim dari dua model tarif yang berbeda, dan keduanya estimasi — bukan tarif resmi kurir, bukan hasil panggilan ke API kurir, sehingga tidak untuk ditagihkan apa adanya ke pembeli.
`tarif` berisi kurir ekspedisi (JNE/POS/TIKI) yang dihitung PER KILOGRAM dari dataset statis untuk wilayah TUJUAN; berat dibulatkan ke atas per kilogram, minimal 1 kg.
`tarif_instan` berisi kurir instan (GoSend/GrabExpress/SPX/Maxim) yang dihitung PER KILOMETER dengan rumus `max(minimum, flagfall + per_km × jarak)`. Jaraknya haversine antar titik pusat kabupaten — garis lurus, bukan jarak tempuh jalan — jadi anggap hasilnya batas bawah. Kurir instan HANYA muncul bila jaraknya masih di dalam batas layanannya; di luar itu kurirnya tidak diberi angka melainkan masuk ke `dilewati` beserta alasannya. Tarif SPX Instant dan Maxim adalah perkiraan dengan dasar paling lemah (kedua kurir tidak menerbitkan tabel per-km); lihat `server/data/ongkir-instan.json` untuk sumber tiap angka.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `origin` | query | string | ya | Id kabupaten atau kecamatan asal, dari /ongkir/search. contoh: 3273 |
| `destination` | query | string | ya | Id kabupaten atau kecamatan tujuan, dari /ongkir/search. contoh: 3578 |
| `weight` | query | integer | tidak | Berat kiriman dalam gram. Default 1000. default: 1000; contoh: 1500 |
| `courier` | query | string | tidak | Batasi ke satu kode kurir. Kosongkan untuk semua kurir. Mengisi kode ekspedisi hanya menjalankan model per-kg, kode instan hanya model per-km. pilihan: jne_yes, jne_reg, pos_express, pos_kilat, tiki_express, tiki_reguler, gosend_instant, grabexpress_instant, spx_instant, maxim_delivery; contoh: jne_reg |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/ongkir/cek?origin=3273&destination=3578&weight=1500&courier=jne_reg" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Estimasi ongkos kirim berhasil dihitung — estimasi berdasarkan data statis, bukan tarif resmi kurir. Tarif instan dihitung per km dari jarak garis lurus antar pusat kabupaten, sehingga lebih rendah daripada jarak tempuh sebenarnya.",
  "data": {
    "origin": {
      "tipe": "kabupaten",
      "id": "3171",
      "nama": "KOTA JAKARTA PUSAT",
      "kabupaten": "KOTA JAKARTA PUSAT",
      "provinsi": "DKI Jakarta"
    },
    "destination": {
      "tipe": "kabupaten",
      "id": "3174",
      "nama": "KOTA JAKARTA SELATAN",
      "kabupaten": "KOTA JAKARTA SELATAN",
      "provinsi": "DKI Jakarta"
    },
    "berat_gram": 1500,
    "berat_tagih_kg": 2,
    "jarak_km": 8.41,
    "tarif": [
      {
        "kurir": "jne_reg",
        "nama": "JNE Reguler",
        "per_kg": 6400,
        "total": 12800
      },
      {
        "kurir": "pos_kilat",
        "nama": "POS Kilat Khusus",
        "per_kg": 5300,
        "total": 10600
      }
    ],
    "tarif_instan": [
      {
        "kurir": "gosend_instant",
        "nama": "GoSend Instant",
        "per_km": 2500,
        "flagfall": 0,
        "minimum": 20000,
        "total": 21025
      },
      {
        "kurir": "grabexpress_instant",
        "nama": "GrabExpress Instant",
        "per_km": 2900,
        "flagfall": 0,
        "minimum": 20000,
        "total": 24389
      }
    ],
    "dilewati": []
  }
}
```

## PDDikti

Pencarian mahasiswa, dosen, perguruan tinggi, dan program studi dari Pangkalan Data Pendidikan Tinggi

### GET /pddikti/all

**Pencarian gabungan PDDikti**

- URL: `GET https://api.apipedia.id/v1/pddikti/all`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariPddiktiGabungan`

Mencari mahasiswa, dosen, perguruan tinggi, dan program studi sekaligus dari satu kata kunci. Dipakai ketika jenis entitas yang dicari belum pasti: satu panggilan ini jauh lebih hemat kuota daripada memanggil keempat endpoint terpisah.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Kata kunci, 3 sampai 100 karakter. contoh: teknik informatika |
| `limit` | query | integer | tidak | Jumlah hasil per entitas. Default 20, maksimal 100. default: 20; contoh: 5 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/all?q=teknik%20informatika&limit=5" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian gabungan PDDikti berhasil diambil.",
  "data": {
    "q": "teknik informatika",
    "jumlah": {
      "mahasiswa": 10,
      "dosen": 10,
      "pt": 3,
      "prodi": 10,
      "lainnya": 0
    },
    "hasil": {
      "mahasiswa": [
        {
          "id": "j2KucYe0YJ7rmlPTEXQUHWC5JYTdJhPDe5gssLrrBFiVqBJXxQIHcTOjRmz722cAFdBIoA==",
          "nama": "BUDI SANTOSO",
          "nim": "0101010033",
          "nama_pt": "STMIK BUDHI",
          "nama_prodi": "Teknik Informatika"
        }
      ],
      "dosen": [],
      "pt": [],
      "prodi": [],
      "lainnya": []
    }
  }
}
```

### GET /pddikti/mahasiswa

**Cari mahasiswa**

- URL: `GET https://api.apipedia.id/v1/pddikti/mahasiswa`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariPddiktiMahasiswa`

Mencari mahasiswa berdasarkan nama atau NIM. Setiap hasil memuat id yang dipakai /pddikti/mahasiswa/{id}.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama mahasiswa atau NIM, 3 sampai 100 karakter. contoh: budi santoso |
| `limit` | query | integer | tidak | Jumlah hasil. Default 20, maksimal 100. default: 20; contoh: 10 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/mahasiswa?q=budi%20santoso&limit=10" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian mahasiswa berhasil diambil.",
  "data": {
    "q": "budi santoso",
    "jumlah": 10,
    "hasil": [
      {
        "id": "j2KucYe0YJ7rmlPTEXQUHWC5JYTdJhPDe5gssLrrBFiVqBJXxQIHcTOjRmz722cAFdBIoA==",
        "nama": "BUDI SANTOSO",
        "nim": "0101010033",
        "nama_pt": "STMIK BUDHI",
        "nama_prodi": "Teknik Informatika"
      }
    ]
  }
}
```

### GET /pddikti/mahasiswa/{id}

**Detail mahasiswa**

- URL: `GET https://api.apipedia.id/v1/pddikti/mahasiswa/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `detailPddiktiMahasiswa`

Detail satu mahasiswa. Balasannya berisi `tampil` (field yang sama dengan halaman PDDikti) dan `data` (payload mentah upstream).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id mahasiswa dari hasil /pddikti/mahasiswa. contoh: j2KucYe0YJ7rmlPTEXQUHWC5JYTdJhPDe5gssLrrBFiVqBJXxQIHcTOjRmz722cAFdBIoA== |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/mahasiswa/j2KucYe0YJ7rmlPTEXQUHWC5JYTdJhPDe5gssLrrBFiVqBJXxQIHcTOjRmz722cAFdBIoA%3D%3D" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail mahasiswa berhasil diambil.",
  "data": {
    "tampil": {
      "Nama": "BUDI SANTOSO",
      "Perguruan Tinggi": "STMIK Budhi",
      "Jenis Kelamin": "Laki-laki",
      "Tanggal Masuk": "9 Maret 2001",
      "NIM": "0101010033",
      "Jenjang - Program Studi": "Sarjana - Teknik Informatika",
      "Status Awal Mahasiswa": "Peserta didik baru",
      "Status Terakhir Mahasiswa": "Lulus-"
    },
    "data": {
      "nama": "BUDI SANTOSO",
      "nim": "0101010033",
      "nama_pt": "STMIK Budhi",
      "prodi": "Teknik Informatika"
    }
  }
}
```

### GET /pddikti/dosen

**Cari dosen**

- URL: `GET https://api.apipedia.id/v1/pddikti/dosen`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariPddiktiDosen`

Mencari dosen berdasarkan nama. Setiap hasil memuat id yang dipakai /pddikti/dosen/{id}.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama dosen, 3 sampai 100 karakter. contoh: ahmad |
| `limit` | query | integer | tidak | Jumlah hasil. Default 20, maksimal 100. default: 20; contoh: 10 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/dosen?q=ahmad&limit=10" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian dosen berhasil diambil.",
  "data": {
    "q": "ahmad",
    "jumlah": 10,
    "hasil": [
      {
        "id": "q5clU1kgAyYeiiZSXt8_LvRR2uu76F1MrsXP1c1kllYJiYOxKDFGSK74fp4Jn4v2m1Mn9A==",
        "nama": "AHMAD FAUZI",
        "nidn": "0401018501",
        "nama_pt": "UNIVERSITAS INDONESIA",
        "nama_prodi": "Teknik Informatika"
      }
    ]
  }
}
```

### GET /pddikti/dosen/{id}

**Detail dosen**

- URL: `GET https://api.apipedia.id/v1/pddikti/dosen/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `detailPddiktiDosen`

Detail satu dosen. Balasannya berisi `tampil` dan `data` seperti endpoint detail PDDikti lainnya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id dosen dari hasil /pddikti/dosen. contoh: q5clU1kgAyYeiiZSXt8_LvRR2uu76F1MrsXP1c1kllYJiYOxKDFGSK74fp4Jn4v2m1Mn9A== |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/dosen/q5clU1kgAyYeiiZSXt8_LvRR2uu76F1MrsXP1c1kllYJiYOxKDFGSK74fp4Jn4v2m1Mn9A%3D%3D" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail dosen berhasil diambil.",
  "data": {
    "tampil": {
      "Nama": "AHMAD FAUZI",
      "NIDN": "0401018501",
      "Perguruan Tinggi": "Universitas Indonesia",
      "Jenis Kelamin": "Laki-laki",
      "Jenjang - Program Studi": "Sarjana - Teknik Informatika",
      "Jabatan Akademik": "Lektor",
      "Pendidikan Tertinggi": "S3",
      "Status Aktivitas": "Aktif"
    },
    "data": {
      "nama": "AHMAD FAUZI",
      "nidn": "0401018501"
    }
  }
}
```

### GET /pddikti/pt

**Cari perguruan tinggi**

- URL: `GET https://api.apipedia.id/v1/pddikti/pt`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariPddiktiPt`

Mencari perguruan tinggi berdasarkan nama. Setiap hasil memuat id yang dipakai /pddikti/pt/{id}.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama perguruan tinggi, 3 sampai 100 karakter. contoh: universitas indonesia |
| `limit` | query | integer | tidak | Jumlah hasil. Default 20, maksimal 100. default: 20; contoh: 10 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/pt?q=universitas%20indonesia&limit=10" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian perguruan tinggi berhasil diambil.",
  "data": {
    "q": "universitas indonesia",
    "jumlah": 10,
    "hasil": [
      {
        "id": "rRll7D_MEhQ_GQ3AoOVVWO_IF4zuTA9F-SU_UsMlDopLGiJhzvb5hmr1B4tcLa53ZievqQ==",
        "kode": "001002",
        "nama_singkat": "UI",
        "nama": "UNIVERSITAS INDONESIA"
      }
    ]
  }
}
```

### GET /pddikti/pt/{id}

**Detail perguruan tinggi**

- URL: `GET https://api.apipedia.id/v1/pddikti/pt/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `detailPddiktiPt`

Detail satu perguruan tinggi beserta payload mentahnya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id perguruan tinggi dari hasil /pddikti/pt. contoh: rRll7D_MEhQ_GQ3AoOVVWO_IF4zuTA9F-SU_UsMlDopLGiJhzvb5hmr1B4tcLa53ZievqQ== |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/pt/rRll7D_MEhQ_GQ3AoOVVWO_IF4zuTA9F-SU_UsMlDopLGiJhzvb5hmr1B4tcLa53ZievqQ%3D%3D" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail perguruan tinggi berhasil diambil.",
  "data": {
    "tampil": {
      "Nama": "UNIVERSITAS INDONESIA",
      "Kode": "001002",
      "Status": "Aktif",
      "Akreditasi": "Unggul",
      "Alamat": "Kampus UI Depok"
    },
    "data": {
      "nama": "UNIVERSITAS INDONESIA",
      "kode": "001002"
    }
  }
}
```

### GET /pddikti/prodi

**Cari program studi**

- URL: `GET https://api.apipedia.id/v1/pddikti/prodi`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariPddiktiProdi`

Mencari program studi berdasarkan nama. Setiap hasil memuat id yang dipakai /pddikti/prodi/{id}.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama program studi, 3 sampai 100 karakter. contoh: teknik informatika |
| `limit` | query | integer | tidak | Jumlah hasil. Default 20, maksimal 100. default: 20; contoh: 10 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/prodi?q=teknik%20informatika&limit=10" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Hasil pencarian program studi berhasil diambil.",
  "data": {
    "q": "teknik informatika",
    "jumlah": 10,
    "hasil": [
      {
        "id": "fZjHK8kthPndZ-6yzkvAJLvf_1_292nPBHgaW_5UqzchhWbOxC2wRpMi10jfk2y3r6cr8A==",
        "nama": "Teknik Informatika",
        "jenjang": "S1",
        "nama_pt": "UNIVERSITAS INDONESIA"
      }
    ]
  }
}
```

### GET /pddikti/prodi/{id}

**Detail program studi**

- URL: `GET https://api.apipedia.id/v1/pddikti/prodi/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `detailPddiktiProdi`

Detail satu program studi beserta payload mentahnya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id program studi dari hasil /pddikti/prodi. contoh: fZjHK8kthPndZ-6yzkvAJLvf_1_292nPBHgaW_5UqzchhWbOxC2wRpMi10jfk2y3r6cr8A== |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/pddikti/prodi/fZjHK8kthPndZ-6yzkvAJLvf_1_292nPBHgaW_5UqzchhWbOxC2wRpMi10jfk2y3r6cr8A%3D%3D" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail program studi berhasil diambil.",
  "data": {
    "tampil": {
      "Nama": "Teknik Informatika",
      "Jenjang": "Sarjana",
      "Perguruan Tinggi": "UNIVERSITAS INDONESIA",
      "Status": "Aktif",
      "Akreditasi": "A"
    },
    "data": {
      "nama": "Teknik Informatika",
      "jenjang": "S1"
    }
  }
}
```

## Kurs

Kurs referensi harian ECB (bukan JISDOR Bank Indonesia)

### GET /kurs/latest

**Kurs terbaru terhadap satu mata uang dasar**

- URL: `GET https://api.apipedia.id/v1/kurs/latest`
- Auth: `Authorization: Bearer <token>`
- operationId: `getKursLatest`

Kurs referensi harian Bank Sentral Eropa (ECB). Ini BUKAN JISDOR Bank Indonesia dan bukan kurs jual-beli bank, jadi tidak bisa dipakai sebagai dasar transaksi valas. ECB menerbitkan satu kurs per hari kerja, sehingga nilainya tidak berubah pada akhir pekan dan hari libur.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `base` | query | string | tidak | Kode mata uang dasar tiga huruf. Default USD. default: USD; contoh: USD |
| `symbols` | query | string | tidak | Kode mata uang tujuan, dipisah koma. Kosongkan untuk semua. contoh: IDR |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kurs/latest?base=USD&symbols=IDR" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kurs referensi ECB berhasil diambil",
  "data": {
    "base": "USD",
    "date": "2026-09-14",
    "rates": {
      "IDR": 17660
    },
    "source": "ECB via Frankfurter",
    "cached": false
  }
}
```

### GET /kurs/convert

**Konversi nominal antar dua mata uang**

- URL: `GET https://api.apipedia.id/v1/kurs/convert`
- Auth: `Authorization: Bearer <token>`
- operationId: `convertKurs`

Mengalikan nominal dengan kurs referensi ECB. Hasilnya perkiraan, bukan kurs transaksi yang akan diberikan bank atau penyelenggara transfer.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `from` | query | string | ya | contoh: USD |
| `to` | query | string | ya | contoh: IDR |
| `amount` | query | number | ya | Nominal yang dikonversi, harus lebih besar dari 0. contoh: 10 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kurs/convert?from=USD&to=IDR&amount=10" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Konversi kurs berhasil dihitung",
  "data": {
    "from": "USD",
    "to": "IDR",
    "amount": 10,
    "rate": 17660,
    "result": 176600,
    "date": "2026-09-14",
    "source": "ECB via Frankfurter",
    "cached": true
  }
}
```

### GET /kurs/currencies

**Daftar mata uang yang didukung**

- URL: `GET https://api.apipedia.id/v1/kurs/currencies`
- Auth: `Authorization: Bearer <token>`
- operationId: `listKursCurrencies`

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kurs/currencies" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar mata uang berhasil diambil",
  "data": {
    "jumlah": 30,
    "currencies": {
      "IDR": "Indonesian Rupiah",
      "USD": "United States Dollar"
    },
    "source": "ECB via Frankfurter",
    "cached": true
  }
}
```

## Kampus

Daftar perguruan tinggi Indonesia

### GET /kampus

**Daftar universitas dengan halaman**

- URL: `GET https://api.apipedia.id/v1/kampus`
- Auth: `Authorization: Bearer <token>`
- operationId: `listKampus`

Daftar perguruan tinggi Indonesia hasil penyaringan basis data domain universitas dunia milik komunitas. Sumbernya bukan PDDikti, sehingga cakupannya tidak resmi dan kolom provinsi belum tersedia.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `page` | query | integer | tidak | Halaman, mulai dari 1. default: 1; contoh: 1 |
| `limit` | query | integer | tidak | Entri per halaman, maksimal 100. default: 20; contoh: 20 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kampus?page=1&limit=20" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar universitas berhasil diambil",
  "data": {
    "page": 1,
    "limit": 20,
    "total": 194,
    "total_pages": 10,
    "universitas": [
      {
        "nama": "Institut Teknologi Bandung",
        "domains": [
          "itb.ac.id"
        ],
        "web_pages": [
          "https://www.itb.ac.id/"
        ],
        "provinsi": null
      }
    ]
  }
}
```

### GET /kampus/cari

**Cari universitas berdasarkan nama atau domain**

- URL: `GET https://api.apipedia.id/v1/kampus/cari`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariKampus`

Pencocokan pada nama dan domain kampus. Maksimal 20 hasil.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama atau domain kampus, minimal 2 karakter. contoh: institut teknologi |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kampus/cari?q=institut%20teknologi" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ditemukan 3 universitas yang cocok",
  "data": {
    "q": "institut teknologi",
    "jumlah": 3,
    "universitas": [
      {
        "nama": "Institut Teknologi Bandung",
        "domains": [
          "itb.ac.id"
        ],
        "web_pages": [
          "http://www.itb.ac.id/"
        ],
        "provinsi": null
      }
    ]
  }
}
```

## Rumah Sakit

Pencarian rumah sakit rujukan (cakupan bertahap)

### GET /rumah-sakit/cari

**Cari rumah sakit berdasarkan nama atau provinsi**

- URL: `GET https://api.apipedia.id/v1/rumah-sakit/cari`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariRumahSakit`

Mencari di 3.151 rumah sakit terdaftar se-Indonesia (ekspor Kemenkes RS Online/SIRS, 38 provinsi dan 509 kabupaten/kota). Parameter "q" mencocokkan nama dan alamat; "provinsi" hanya mencocokkan nama provinsi. Datanya snapshot titik waktu dan tidak memuat nomor telepon — verifikasi ulang kontak sebelum dipakai untuk keperluan darurat. Isi minimal salah satu dari "q" atau "provinsi".

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | tidak | Nama atau alamat rumah sakit. contoh: cipto |
| `provinsi` | query | string | tidak | Nama provinsi. contoh: DKI Jakarta |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/rumah-sakit/cari?q=cipto&provinsi=DKI%20Jakarta" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ditemukan 1 rumah sakit yang cocok",
  "data": {
    "jumlah": 1,
    "rumah_sakit": [
      {
        "nama": "RSUPN Dr. Cipto Mangunkusumo",
        "alamat": "Jl. Diponegoro 71 Jakarta Pusat",
        "wilayah": "Kota Jakarta Pusat, DKI Jakarta",
        "telepon": null,
        "provinsi": "DKI Jakarta",
        "kabupaten": "Kota Jakarta Pusat",
        "kode": "3173014",
        "jenis": "Rumah Sakit Umum",
        "kelas": "A",
        "kepemilikan": "Kemkes",
        "tempat_tidur": 1542
      }
    ]
  }
}
```

## Sekolah

Pencarian 215.373 sekolah SD sampai SMK se-Indonesia (data Kemdikbud)

### GET /sekolah/cari

**Cari sekolah berdasarkan nama, NPSN, provinsi, kabupaten, atau jenjang**

- URL: `GET https://api.apipedia.id/v1/sekolah/cari`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariSekolah`

Mencari di 215.373 sekolah SD sampai SMK se-Indonesia (data Kemdikbud lewat API publik api-sekolah-indonesia, diambil 15 September 2026). Isi minimal salah satu dari "q", "provinsi", "kabupaten", atau "jenjang". Hasil selalu berhalaman karena ukuran datasetnya.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | tidak | Nama sekolah, NPSN, atau kabupaten/kota. contoh: SMAN 4 JAKARTA |
| `provinsi` | query | string | tidak | contoh: Jawa Barat |
| `kabupaten` | query | string | tidak | contoh: Kota Bandung |
| `jenjang` | query | string | tidak | SD, SMP, SMA, SMK, dan jenjang lain sesuai data Kemdikbud. contoh: SMA |
| `page` | query | integer | tidak | Halaman, mulai dari 1. default: 1; contoh: 1 |
| `limit` | query | integer | tidak | Jumlah baris per halaman, 1 sampai 100. default: 20; contoh: 20 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/sekolah/cari?q=SMAN%204%20JAKARTA&provinsi=Jawa%20Barat&kabupaten=Kota%20Bandung&jenjang=SMA&page=1&limit=20" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ditemukan 1 sekolah yang cocok",
  "data": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1,
    "sekolah": [
      {
        "npsn": "20100211",
        "nama": "SMAN 4 JAKARTA",
        "jenjang": "SMA",
        "status": "Negeri",
        "provinsi": "D.K.I. Jakarta",
        "kabupaten_kota": "Kota Jakarta Pusat",
        "kecamatan": "Gambir",
        "alamat": "JL. BATU NO. 3 GAMBIR",
        "lintang": -6.1788,
        "bujur": 106.8356
      }
    ]
  }
}
```

## NPWP

Parsing struktur NPWP 15 digit dan 16 digit

### GET /npwp/parse/{npwp}

**Uraikan struktur satu NPWP**

- URL: `GET https://api.apipedia.id/v1/npwp/parse/{npwp}`
- Auth: `Authorization: Bearer <token>`
- operationId: `parseNpwp`

Membaca struktur NPWP 15 digit menjadi golongan wajib pajak, nomor urut, kode KPP, dan kode cabang. Masukan 16 digit diuraikan sebagai NIK sesuai ketentuan NPWP orang pribadi terbaru. Ini validasi format, bukan pengecekan keberadaan NPWP di DJP — struktur yang benar bukan bukti NPWP terdaftar atau aktif. Untuk masukan 16 digit, `format` bernilai `16-digit` dan `struktur` berisi `{ berbasis_nik, nik }` dengan `nik` hasil penguraian NIK (atau null). Masukan yang bukan 15/16 digit dijawab 400.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `npwp` | path | string | ya | NPWP dengan atau tanpa titik dan strip. contoh: 012345678912000 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/npwp/parse/012345678912000" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "NPWP berhasil diuraikan",
  "data": {
    "valid": true,
    "npwp": "012345678912000",
    "format": "15-digit",
    "errors": [],
    "catatan": [
      "Validasi format, bukan pengecekan keberadaan NPWP di DJP.",
      "Golongan wajib pajak dari dua digit pertama bersifat indikatif; DJP tidak menerbitkan pemetaan resmi yang bisa dirujuk publik.",
      "Digit kesembilan dibaca sebagai digit pemeriksa secara struktural saja — tidak ada spesifikasi checksum NPWP yang terbuka untuk diverifikasi."
    ],
    "struktur": {
      "kode_wp": "01",
      "jenis_wp": "Badan",
      "nomor_urut": "234567",
      "digit_pemeriksa": "8",
      "kode_kpp": "912",
      "kode_cabang": "000",
      "is_pusat": true
    }
  }
}
```

## Rekening

Pemeriksaan format nomor rekening dan daftar bank

### GET /rekening/banks

**Daftar bank dan kode kliringnya**

- URL: `GET https://api.apipedia.id/v1/rekening/banks`
- Auth: `Authorization: Bearer <token>`
- operationId: `listBanks`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | tidak | Saring berdasarkan nama atau kode bank. contoh: mandiri |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/rekening/banks?q=mandiri" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar bank berhasil diambil",
  "data": {
    "jumlah": 1,
    "banks": [
      {
        "kode": "008",
        "nama": "Bank Mandiri",
        "panjang": [
          13
        ]
      }
    ],
    "catatan": "Pemeriksaan format dan daftar bank, bukan verifikasi nama pemilik rekening."
  }
}
```

### POST /rekening/validate

**Periksa format satu nomor rekening**

- URL: `POST https://api.apipedia.id/v1/rekening/validate`
- Auth: `Authorization: Bearer <token>`
- operationId: `validateRekening`

Memeriksa nomor rekening terdiri dari angka dengan panjang yang wajar dan kode banknya terdaftar. Bukan verifikasi nama pemilik rekening — nomor yang lolos pemeriksaan ini belum tentu ada.

Body JSON:

```json
{
  "bank_code": "014",
  "account_number": "1234567890"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/rekening/validate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"bank_code":"014","account_number":"1234567890"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pemeriksaan format rekening selesai",
  "data": {
    "bank": {
      "kode": "014",
      "nama": "Bank Central Asia (BCA)",
      "panjang": [
        10
      ]
    },
    "digits": "1234567890",
    "panjang": 10,
    "valid_format": true,
    "catatan": "Pemeriksaan format dan daftar bank, bukan verifikasi nama pemilik rekening."
  }
}
```

## Vision

Pemrosesan gambar: hapus latar, cocokkan wajah, liveness

### POST /vision/background/remove

**Hapus latar belakang satu gambar**

- URL: `POST https://api.apipedia.id/v1/vision/background/remove`
- Auth: `Authorization: Bearer <token>`
- operationId: `removeImageBackground`

Menghapus latar belakang gambar lalu mengembalikan PNG dengan kanal transparan. Model: isnet-small. Gambar (PNG/JPEG/WebP, base64 atau data URL) maksimal 8 MB setelah didekode dan tidak disimpan permanen. Error: 400 gambar kosong/bukan base64/terlalu besar/bukan PNG-JPEG-WebP/tidak terbaca, 500 kegagalan pemrosesan internal.

Body JSON:

```json
{
  "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/vision/background/remove" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Latar gambar berhasil dihapus",
  "data": {
    "image_base64": "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",
    "format": "png",
    "model": "isnet-small"
  }
}
```

### POST /vision/face/match

**Cocokkan dua foto wajah**

- URL: `POST https://api.apipedia.id/v1/vision/face/match`
- Auth: `Authorization: Bearer <token>`
- operationId: `matchFaces`

Membandingkan dua foto wajah lalu mengembalikan keputusan cocok beserta skor kemiripan 0-1. Hasilnya skor kemiripan, bukan pembuktian identitas, dan tidak mendeteksi apakah foto diambil dari orang hidup. Gambar yang tidak memuat wajah terdeteksi dibalas HTTP 422, bukan "tidak cocok". Menerima JPEG, PNG, dan WebP sampai 8 MB per gambar.

Body JSON:

```json
{
  "image_a_base64": "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",
  "image_b_base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAD6AMgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDD8IxvNKkSHkzLV6/0y/0SXUbKch45nMwZQccnpWd4RlMdyNpwfMBBFX5/EWrX8l/b6h5ixSIAoKYDYJwc0qMXKlNHoVZctamVtHONOuB6MKqWJ/4+T/tH+dWNJP8Aol0Peqmnn5bg/wC0f51wno9jRkP+lxH3p0Bx4mf3RaZJzPEaSE/8VP8AWMUimaV3zBOP9qtbwdLHDqcTygFfLbg1k3P+pnHvUuliVmjEH+sI4pwdmgrq9KS8j0CTUbqQDy0VU7ACq02n/bnE94+8HoorOsrvVIyA9sgAIHXtUt1qd/Cz7baNUyMSE9c+1ddpvqfN+6i7HptrPHJbGPbFvB2rT7zUdPszH5lzHGE+XaDk/kK5LUvEUhUww3QVW5dkB/Wua1K7UqoPzyEfLu+9VLQOS56L/a+htc+bBdRgsPm3AjP6Vq21pYtp81xA8UhYEkoQea8Vt5J03bQnIOGAPHTvW1pWqSxy7UuXjnJGSrYV/Y+/Wr5kyXTfc9YisjJCryHgjpTpbFpVC7sJjp61maL4nhubNFuwI3A4OeD/AIGtBvEOn+SHaZQScYz0q03YwcVfU4TxjGsGo+WqFAI+/euIc40+b/ers/Gl9Fe6mZIWDKIwMiuLl/5B0n+9Xnz+Nn0tD+BH0Ohumz4eY/7AotGzpVsKZcH/AIptv9wUmntu023rPodS+L5C6sf3F6P92mXvOjD6il1X/V3v0WkuOdIApg+pY8M38a+NbWykbC3NnsJ9GHI/ka0vGdp9luLqYfduIFOfUjg/0rhzdGz8a6fLniMxfkf/ANddr43uTJcyQ9kt1P5mumpG0EzyaUm8S/mc7fnOo6aPSMUUl7zqtiPSIUVzHqrdj/A1zbW19M9yRjYdnu1djH4m8P6rcagl0qiGOzSNXcZ3Nz0Hrmua8E6DZahpN3f3M+ySCTCAHHbvWrqXgbShNdS2V06/ZbVZiqtuJbBOPavTw8YctpdTwMTNuaa6HM6XxFeLnOKp6ef3Vwf9o/zq5poI+1ggglc4NUNPP7m4+p/nXmtWbR7ad7M03P7yI1HE2PE4/wCuYp55aKoEOPFC/wDXOpKZt3B3Rz/Wi0leGNJEOGA4NJMfkn+tP01dzwiiKu0XVdqbZNJf6sssccr+UXYKu7371Svrq9nuPJe4LmJtuVbjJ4P86u+IGt5L6HZM7tj94ew9qTSNOc25kk6s5+VuvXP8812TtDY+dp3na5q6bo1rDArtEHkYfMTzVuXTLKVh5lpGxByCV5q5ZqVABHI4xU8oBPSuBp73PTVlpYw20TTtxItEBJzxkVLbaHpBJV7NVLZ+YEitBtvBAH0qFnJBwvNKMmnuOUYtbFPVNOisPLngySWwUHG5f/rYrU0G5stRsw1xGhnjcxv8vUjofxFZd9K0kSZGdjZI7VmuNQt7+U6WsjPMVcgLnqP8a76FS+55mKpWQ3xrHHDqzpEAF8sHAFcZOf8AiXP/AL1dBrbag1w39phluAvIYY47Vztwf+Jew/2qxnrNnrUVahFeRvTtnwyx/wBgU3Smzp8H1ps5/wCKZb/cFM0Zs2MX1rLodSfvL0J9UPy3n0WnMN2mKPWotTPF5/urU8f/AB4J7CmhvqcdrUm3xIG/ubP0Ndbr8xl1S+JIIMceMemBXEa2+7WZG9hXStqJ1JWmZApESJx3x3rtrr92jx8NrWZNd/8AIYtPaIUUXf8AyGrcekQoriPXW7J9B0HV/sdndqCLCeQs3OPm6A4pviHQ/EGguxeZpIrld8zxMeOwBP0q5p/i2+8jTtGkhwkIMjM64LAcrioNQ8f3dxbrZ6haJcLJNuw3y/IDwPevdpqcXa21j5eq1JX82VbOG4tpriK6Rkl8oEhuvTis3Tz/AKPcfU/zrqvEGq2mr+Iri5sh+6+zKmexIH/165PTz/o1x9T/ADrxqqanK/c9+lLmhF+RqqciKqxOPFMf+5U8JyI6qyHHiiL/AHKzRs9kb8xyk/1q54es11C8htmfywyMd3pVF+Vn+tS6XK8U0LpnKg9KIfEh17+ylbsyG8sHtbyaIyecqMQzqCQPTPpW34XUvpwlk5fcwI9Dmm6W1nqmo3RVQsW1hJuOASQP/r1bsI0ggnS2bfCZWMZB6g46H65qqst4nl0oe7GXdC3PijTNIlMdwzySDqsa5I9qrw+ONMv5PLijmRz/AH4yKqTaVdbpGtpUtEIJyke9y3uaoWWi6mJ42NxNIUYF5JFAD/h2pPl5Slz81zqp9QigBkb5VxnJrnp/G9tDKYhaXDDOMqnB+lbesxmax8tVAYrjI9a5I6Tfhl/0u7iIbJeNs7h6YzWdPlb1NanNb3Tp9O1Ow1m3ZrVjvXh424OPpU1jePZai2yJndAEwq5JHX/CsTRrC6tdTjnf96pbBkZQjYPUH1q9qkMRu791Y+dtB4PQbR/hWkZqm+ZGTpOsuRmd4xvRfas8wOR5YArk7j/jyYe9bWs3X2q5kl2BO3Hf3rEuP+PI/Wqk7yuddOPLSUfI2Jz/AMUw3+4Ki0Js2iD3p1wf+KYf/cFQ6A2bdR7mo6G/216FvUz815/uipGfZpQPtgVDqR+a7/3RUd7Ls0y3UHl3A/SqgrtCqO0ZPyOO1ds6m5/2BWzpT7rY++KxNV5v294ga1dEYmJlPYiu6qr0jyMM7Vjcuj/xO4vaMUUlx/yHB7IKK889ldTor3X9FudX0KGziA8iLfKNuflxwCax9QvdI1rXoGVkRJr2MYkTBCA4IHtn+dWbjw3p+n/ECGytJGELQlnJbO3PYViT6A03iqOws2ZV+0Msb5BIxzn8692mo3dn0Pl5X5UdN4m0mw0jxDJFpxHkywbyobO1u49u1cbp5/0S4+p/nW7PpN5pGsXVveyiaYIGdwc9RmsDTz/oc/4/zry8SrVHrfY9vCu9KPzNa1OVSqc5x4mh/wByrVicqtUrk48Swf7tcy3Op7HQseJ6bAxES4coSMZFKf8AlvSWlwlsEmeFZlUElG6GlHcup/DZp2Nt9jka1yYnuYiBnueoP8617KMQQQxN8pVcMD65zWYPEyXeyRdISWSIZBXqmO9PstWGpTkm3aN5ATvJ+9jGB+H+NXWg78x5OHqe7yM3bpYUjMu8KvU57VU025a/jNxFGBbbiqO7csP72PSsjUhNdPHavIY4WBLnOMjIGP1q6zaU0KxieH9yNqhX5HtxWSjzHU5W0NC62BWR3UAd81kvdCxuES62mOU4WRTkKewPp9aozG1cgTXu9f7pbP8AKq811pyw/YzLEA44QnaT9M0uQfPbc6uJothUEHcM5B71mat5kc1xdquAYto92xVLR/PygkkLLuKhj3APWoLnxC8Uk0QiWWJpSwDnIH0qoQ5mZ1KnIrrcxbuR3jzJ94Lis2f/AI8vxrS1G5Fy7SqipkdF6Vmz/wDHn+NaP4jeD/dr0NS5P/FMv/uiq/h8/ul+pqa6P/FMv/uiqvh9vkH+8anoa/bXoX9QOWvP90VS1GT5LJPRXc/gtWb9vmvP90VVjTz9UhebK2sFu3mSYyEJHetaKvIyxMrQYugeG7bU9dVdY8yG3l08ywuCArYHOSfrXRa0PDl3Ek+ktCJ44YlYq/LEHBG2uI8S61JfSWtkCNlpAVDrxvz3+lU9EbGoW/u2DXbKPNBnkUnaqn5nUT/8hv8ACikkOdZb6UV5p76Cx07VYPF1zDePm6VCXO7OAf8A61Ztl9vsNQi1G2DSGKRyNpJztPP4VoaRrt3f+I7vUnCiSUYbjgjtn8K1NA8XQWDRxajbJ5UHnGJlTHBPQete+uZN6HyztyIrQa7e6s90dRO+ZwpDbMEYzxWBp5/0O4+p/nXdaTqWh3Wia/KzMJ55g0XmAZ5HygD2rhLD/j0ufq38683GK1S9rbHrYGV6aXa5qaccqPpVO7OPEkH+7VrTD8o+lU7048RQfSuJbnfL4UdET/rvpUaMVtwQMnaeKcTzL9KjjYrCCOuDSW5pU/hssWl1cWFo5hjbM3yhsUthdTQzRhztWN84Ix14NRm+cqmGIEZDBfeq13cvLKZBkBua62ro8CLtqdbcQxXihJFBQthwf7pGD+uKl0S1k8O2b2VrDby2ZdnQTDlc5yM/iaiXMaRtMMeZGpYehI5rQcEwbGOVI61yQbjoeg1GYTa/G7u0dhaxSMQxPXJH0Fc1dwNe3f8AaN8qSSxKUgUKAIx7D16VoPZRLKWUgn61Dd4jQMevRQOpPam5tgoRj0Kst2LW1faQNkexPr6/icmuelkEsucBVHQCun8R6IdK0fTZrlwt1dzOrx5+6NoIH4Y592rnmt5POMagNt7jsK6KcHCOvU5KlSM5adCtcgqnK7cjOKpzf8eg+tX9RlSRxszgLg/Ws+b/AI9B9axe56NP+GjRuz/xTT/7tVNAPy4/2qs3h/4pt/8AdqloJ/8AQqXRmv216F++PzXn+6Kn1XzdD0y4tdqML6xEpf8AiU9MfSql63N6f9kVgHULjUTqFxcOXYW6oCeyg8V0YddTjxstkQSWs17rKW8C7nkiXA/Cr2n6ayah5YRy8JyQoycg1N4eXf4jhc9rZTXQ6Pqv/CNePLqdoRMrgqUPHBwQRVSqvn5PI4nC0efzKbZ/tV85zznIorR16+stS8SPe2KlY5YgWUjBDd6K5WrM9mnLmipEGg2seiXOp2l4CswcLHgZJB6Gult7Hw3q3gyCGT5WgeRh5jAPvyeM+hNJ4vsHF7aTxw5LnYWHUnPArJ1rwJdDR9MltXLXNzIVmjVsoOp3Z9q9mMlLVuzPnZRtFWRzuhadNqmrR2cBEbO5HJ4GM1Wt4mt0vYH+9HI6n6hsVowW9x4a12NJTiS2lVsjuP8AOar3rpJqOqSRfcklZx+PNcWKrqrKy6HtUcFPDU4SbvzK5NpR+UfSqV+f+Kgg+lW9IPy/hVLUTjXoPpXGtzd/Cjod3zyfQUyJgI1z05zRn55P90VGrhbfcSAMHkmlFXaKqv8AdyuaVysKWuWUKTyuRyawrq4EUDu3QAmpJ9fZSpjAkdV2qzDIH0rB1G4knifc2WYV6Cp33PnVLlPedS0T7ZpNvcQKPNECb1H8Xyjp71ysh1G0jKovnx+nQj869ItE8rT4FcgbIlDE+wrz3XfE3h6fXYrbfMbYti4uY1DRc9P/AK5/Q1EsO56wRrSrqOkzHi1K9vbryLWzZ5ScYHQfWuz0Hw8bR1vL9xPd9VA+5F9PU+9bNjp+n2lspsYoxG6hg6YO8Hoc965H4leK/wCwdLGn2b4v71SAQeYo+hb6noPx9K0p0Ix1e5nVrynotjj/AB34pXWvFAht33WenZijI6O+fnb8wAPp71mxairBnWQRN0bPRq52DCjinyPmGTHpWjSa1IWmxuTjCKeoI6jkVWlP+ij61Rsbto14PUcj1rSU29zFsZvKPZgOPyrmnh3e8T0aWMio8s0Wbw/8U4/+7VHQTz+NXrrbLpDW0UiSSEYABxmqWkwy28pSVChzxnvXO4SjF3R2qrCc1yu5avnCrfE9Ntc/pFrJdWOoeWVX5RkscDGc1samk1wZ7eBS0kpAAFSCOPRPD3ECyTHPmB/XFaU5csbLdnFi5XqW7EPhiPzNeix0NqoyK6qbSZrfxTNqU2lSahZlAny84YDrWZ4Ws1F9ACU80wqTjv6DFd7HqcWkx2UVwJYWvJWAd1wob0PpWa96u2RU92mkcNeWltBftJZFzbkkZcYYNjJGPaiuk8b6SYbhNUiH7ufCyAdFfHX8aKmaalZnfhpXpI19Ymt9QsLaaN1dGkDoc9ay/Cl1dNrV7Y3BIigJMSdgGbJNYfhxzd6bZQoxbyC7MM8Cu5SK3t7Gz1HaqOxETsB1B6frXXTqaPzOOvRUH7O+zMHx/oyvYRavEvzxt5cvuD0P5154o2rce65r23VbMX/hm/tyMloiR9QMivEz0m/3K5qitO/c9bDVfaYFxf2X+DLOjnI/Cq9/BI2rxzAARoOWJqvHePbQAIcFsDNRy3DudzNkmtqdC/vM8uti7e5FGnPquGJjGMjHNZlxeSSHLEsPSoSx9adgdvzNdcYqOiR586k5u8ncf5uFBHPFLZw/a9TsrZj/AK+5jjP4sB/WoTzVvRQ7+JdJWNdzfbImC+uGB/pTZmeu/EnxHcWVtFommOEuZ8PcP18uIHp/wLH5A+teZ35ENw7SR7XLKxt1yEdcfeOD1r0Dxs1lYzzyyFI7u8VlWRhnYo6u2c+mAP8ACvN9HkF2qO6qu6Zsbzyc4C4+gau2haMDnndu53vw01y9ttNvbXUkcaZbb5bW4YYCqMlk57dx+Neba/rE3iDXLnU5sjzn+RT/AAIOFX8q7zxzqMWj+GYdGtCFacbDjrsHLH8eB+JrzPFck9zaHccjhRgjimNcrkxqNxPBA7fWg9KYEVcbRjceg71BoWIVCqB7VOJdvHWq/SlzmqEWluWB4OKvW2osnEnK+4rHBA5PSmmVnbqaL9wWjujpdOlMmsedE6lSh3IfvfhVPxAWkit4gCVeZs+5rMSYwSLIkuHU5HNbeqkP/YgAK+ZcbsEep9a5JU1GpdHRFyqNykbmgSHSfFdjcXCqE8gD95wBxit7UtOj1x5bOS+aUMxeEmUNs9xj0qLRJA/ilXMaTpDHIGU845HSuyfT9LgkN7aWkUckg+Z1XGRWUqbkt7FTmlM57VFnfwLLBduGnswEkb+9j7rfiKKi1/U4r+y1L7GymCOERSnP3nzkYHtRUVHdnZhG1BnK2jz6DoEEuBDL9pMUpI/hJ5rtfEUflfDqYxS7jHskR/8AgQNcd4/uLiG8GniNRBJiUNjknoadaeJVk8B6ho92/wC8RB5BP8Qz0/Cq5krxOyOGqVYxrpdU36XPR/DN6up6DBcZ3ebGN31715T4s0k6Pr95bD/VuvmR/Q9q6b4W6ucTaTI3bzIv6imfFmNIZrG6x80iNH9ehqkvaRTOao3hK06b2f8Aw6PL5m/eRgdN38hTieajk++h/wBr+lLn5q7keMxx6VITUR6U8c80CDtW14IiE/jvSVbG1JTIxPYKMn+VY3atzwfHKmoajfwkLJa2DiNicYeQhAfyLH8KaV3YG7I0fEeoNr+pXNwAGa7YNBmTAiRegOeMkD9ayyq2enGUMxiMhMQ3BSku1ctgds1fuYlk8hB5n2aUkwJuUsZOBlvYmqmvzvHFImMXEhKXI2rtHPAXH0r1nFQj6HEm5Oxj6nqdxq1ytxcHLLGqAZ6Y6/rk1WhdI5o3kj81FcFo843gHkZ7ZptIa8hncjW1nW9N1CxSCz0C206RZNzTRvuZhg/L0Hc/pWQi7fmI+Y/pSKMtnsv6mlBySfSklYbdx3cCmvJyEXqf0prv5cZc1DGH2bv425J9Pai4iZnUcE8UCSM8GXYPYVF5APLc/WlCxRkfLk+gFAy7FFp7jJkfdjqRwa7DxVo/n6RpiRuYXhhVwT64Ga5CPz3geXYpEK79gHXFdXeatrWu2ltNHoUy26pjzAQQR61jW5rXib0HFNqR0PgOznjhW6nQIXjIHq/PLfStTWtXmt9DktoR/pEr+XGfrUejOUubOS8m8pmiAEEfPydh9SazNZkjmE1lJJJEUlJIAyetYupFU7Nl1aM/aqUVoykIbZrS5mtbFljFuUbe/wDy0HVh60VbvWM2lFYxiNYztUUVwqo5NnfShyxsbnijRU13Q2eNQbiLLxMO/t+Nea2GnvqMzWoysm1iBjuO1eoeDJ3uPC1m8jFm24JP1rP1Tw8NO8V2+qWy4guCwkAHCtjr+NdU4c1pHVgcW6HPQk+9jivCd4NM8S2c0p2Ksmx/bPFa/wAWtXhudZtdPjOfskRaQ/7T4IH5AfnWZ4q0xtO1h2CkRXH7xCP1rntVvJ9R1CS4uX8yUhVJ9lUAfoK0w2knFkZwozp068euhRdgGQerCnEfPVeT5GGcgZH0q0R8+a7T50TvTl6U0daXpn60wHZ4rX8POCLu353yvDsULnew3kL+dY4NWtHm26xDbs6xLMcmVs/uyAcHj6mtKX8REz+FnSS4eWSRhhZSBdP5PEBLdF/KsHW5S1xFEUCiOPhguC4JyCfwrZd0AWVokwu1WtssDLwTvNcxPKZ52kYk5PGTngcAflXbipWhbuc9FXlciIpjZyAOp6U89KYDgFz1PT6V5p1iMQAEXoKUL8oHrUYYA5OacZsghVI9zQBXvXyVQetSiUfdRC2O/aqVwxaYCrkMYRAW/DNSnqN7Eg3NycClEscbdNx9BUbAN1bj0FKinPypgflmmBfhluJowiIEjd1Qgnrk45Nez6PaJFpa2qgKgTaNvQcV4vaNGWxcXCovZevPqcV6x4F16HWdMXzJYxdDOYlP3VHGaiorocTE1S/uvCpSVrUv5UuTPnJ2ngAVhSeJ7KbzZWuNzyEtz1ya6j4oK39gNs5zIucfWvI7Wwu7sSPb27yrDy5UfdFcyw8J7vY6frEzub7xfaJbi2sx5rsQC3Ycc0VxYgmgkQywyR5PG5SM0UnhoR0RtTrSktT1H4c63sLaNPx1aLPr3FeiXqB7CTIzgZFeQ+Is6N4weazARkKyAdsnrXpVpr0GreE57+3I3pCxdO6sB0pUpbxfQ68fQb5cRBaSt95z/wAQIFk8EpdCMGSKZAH7oGyD/QfjXkbBkbeoOT1A6GvRfH+ted4f0qwifC3I+0ygdwOFH5lj+Feesx7DiuyC0uePVk78r6FO5Jdcngjmr3UA+tUJ4WfcwLDuQeauRPuiQ+oFUtzNj6Ruv4U12ORSsflB96oQA81JZSRx6zbtMhdPmG0NjnBx+tQqecUsUjxajbuhxzg8diMH9KqDtJMUtmdPfXE0EM5M267jGGmEgIKEABR781zQ4Fa2sOyW0cUYP2VpGaEsAGYd84rIBGK3xMrzsZ0VaI1vmbZ26n6U1yN2T096Bym/oW5/DtTDwc8VymwFh6E0xm44GKC49ajkcAUhpEcKCS4Ytzt7VbKrnlck9Bmtrwh4Ql1djf3zm101Tl5TgFlHXbn+f86r3trDqOvTweHLOeWJ32wxjLkD1z6fWs4zWxbhK1zMaUg7I1Ge57CnJCzcuSanuLGbTLuS0vImiuIjh0ccg01fOY4XCitFqQ9AmgUW8hUH7hIqHS9XvtIeWSxnMLyoY2IHY1qWNsJ7qK3kuPLWVgjSYztB4zVnxh4Wh0W+I06RriDYGfauQmeOv4UOwI6XwJq17rs10+pSJcBERER8YG3viorq3dnvX07yl/e5lCN2z6V53a3M9q7yW8zxOBjcjYNRx3M8bFkmdWY5YhjyfeuJ4V+1c77nR7VcijY9M8W3stt4dKXLTH7YUEG5EG0DqCOoorgLzU73Umja9uHmMa7ULdhRXUo2JhI9R+IGjBo4tYhHI+SX6djXK6ZrVzpK3KwnMdxEUkQng5HWvT9ECa14VtkvQJFuIQH968x8S6LN4fvpbeUExn/VP/eH+NcE4NyUo9T6PB4mCoTo1deW/wA0jGu7ya9eMyvu8qNYk9lHQfz/ADqqwOfSlwc8UP8Ad5r00klY+Wk3JtsrSyFSVFNtpcRbT1UmmzuFPUHFa+g+D9T1hRc70tbeTlXkySw9QB/9as5TUdWVGDlokZzSgrmnqRJF6ZrodQ8AXlpF/ot5Hct/dZdmf1Nc75U1nI1tdxNDKv8AC4ohUjPZhOnKG6EU5IPrTZ2KzQuBuKuDj15pFYBiM+9Nu2/dhlOCDkGtLkGvrEga5B4DFctGBgRkk/KP0rPfkBP7xx+Hen3cvm3TMW3E9WznPvUSnc7N2X5R/WtKkuabZEFaKElcjgVUcsT1NTSsMmq7Ng1izRCbjXWeEPC8Wp7dS1NW+xqx2R4/1pHr7dves7wv4an166DvmOzVsSSd2/2V9/ftXq7mCzghtLaAFlXCIo+WJB/n8a5a1W3uo7KFK/vPYhm0W31BN+oSeVZIuEtY2KrgdN2Ov06VFYXSwFbTQ9PUDcV80YjjH1bv+GaeZrC0U3Gp3Bkcc7ZW+Vf+A9P51Xe/1vU7e5m0XTxDbxqW8+7Plh8Doinkn8hXLG7Ot2RyHjuxubLxEs11dR3T3UQYNGpAQL8u3H9e9YClj14FLc30+oTG6uJTI7/xv/IVGrwg5Z2Y+1enBOMUmeXUkpSbRO7FIHYHBCHkfSpdE8QmysLzT7ku0V6AplzlovcVd0PS0164nt3YxRrAzFvfoP1P6Vzt9ZTadeyWs4w6Hr6j1p3TfKSk7XNvxLpFnBcouiq9xEsCmV0XIB9T6da577JcKoZoJFUEZJUgDPSux8K+MNO0XQW067s3maS53M6tjavB59fpS6746tL/AEgWNtp6K63BcO3PGeD/APWoTYFjQvh7NILO+1V1WxuDw0bDgbchifTPFFc9e+J9V1DSodMuLlmt4X3Kq/Lx6cdhRSabKR6p8PdTaWwfTJl2y2bY+oqp8WbqJdO0612gzSStJnuFUY/Ut+lc9Hq0mh+Jlvo+Y5FUyKP41PWo/iHqyap4ijaBw8EVtGEI6fN8x/mPyrjwlT2kEz0sxounL2i2l/TOTeRYvvHn0qvNdqylNrEEdqmleNRyMtUAYscrEPriu88krW1v9pvIoN3EjAZ717VpUSR20URTCoAMemK8jWDOH2qrLypA6GvRvCesx6jZbHyJ4fllU/zHsa4cVCWj6HdhZx1XU69IUZAygY7DFUdWsoLuWKzmsVdHUvLcSxBkgjAJZsnvgHA+lX4JomIQFT8uBzjBq14y2WPw71J4hh3ttpfudxAPP41NGkpe8wr1nF8qPC9SuLe91Ca4tbWO1t2bEMKLgIg4XPqcdT3OaqPGjjay5FO4pK9A4RvkqeRn86YVKJhTkDrmpgaZKCPmHagBjWysud5Jx2qsbSVuVIx7mrIYg5X/APVUisCMjp6elK1x3Oz0PxJpGj6ZBZoZtyLhm8rqep/Wrdx4ysmi8uykKySkAvKrAL7muCzSbqweHg3c6FiZpWO9ttX8N6aPtc8rX99/CSN20+oHQfzrE17xzqeqKbeMfZ7RhgiM/M49z/QVzxfIqPOVwacaMYkyrzl5DwFdd6/MKTOTgLTS/lsGTgN1+tP80yYOABWxib3hdbr7Xst43bziIyQOBk960/GWimWyMioDcWpOSvO5e9bvgKe304Wdk0RNxcI0rkjoW5H/AI6BTba8gfUbiKYjcXdtjemea5Kl+fmXQ1i9LHka/cb3NRr94fWtfxBaw2er3MUAwm7cB6ZrHX7w+tdSd7MgsiigUVTBHZSxPfeHrS9RdxiGyQjsPWsG8UiQP/eFdN4KvoprKTTJBkkkgHuD1rO8SaU+lThPvRM2Y29vSvBwlX2eJdF+dvQ+hrtVcC091qjCEYzkjmpB7CkJFRNJK3EQx6uele/sfNEzEKMu4UUtpf3Njci6sJWSQDBf+Ej0Oeoqkyxo2ZGM0nv0FSJukOW6elS9dGNaao2l8banZN5mIfNzkMoIrqLv4i/8JR4N1SzuYkguVji2AHiQbxuwPbivNbld9wWk+WNAPxpbZ90rPjCgYA9KiMVF6FSblqy2TTdxXnqKN6etIGUnhga0JHLIp70Fx1qMop5zim528A5FACnCnI5U/pSk7W3DoeopmSOV5HcUAhhgflSGTBgRxyKM54qD5kORyPSnhw/saAHHjmjIC0m71prsMUALnKEde9W7COBrqFZ95t2cGTYMtt749+1Z+5iwVDg+tbejF1leWO7treQjaPOO3I9qlsaR3fhfUrXUvFcksFu0QWJmRGXaBwABVLxRYXcHirRFjgEDXMrKSGB3ZNR201tbOJk1O08zbgnzapatfWk00d1JeJcXFqC0WyQnaax9nK5XNEwPGto1h4murZzkrg/mK5xfvD612Eut2F3M09zplvLK/wB5nBJP603+0dH/AOgNaf8AfH/162WiRN/I5ntRW1qNxYXVuYrTTIYZCch0JBH60VV0CJfCz7NatDuwvmDP0rttZSDxDoF4loRJNAxeNR1yp5H5ZrndM0O2ln2wecNp+8G6V1OlaFBpLSXCTy5ZTuVjwa8PEUZVXGpT+KOq/wAj24yhGMoze55a0hQ9M1E7zS8D5R7V1dxpGlSzOym4d3csfn7k1PH4Pt5Ld52mmgRRnJwa9ZVk9zzZYWcVc4+OEJ161I8m0YUZNXpNNYMwhuEkAP8AECp/rWaZ2U4Eag+ua1UovY5pQlHdEZtpJn3ScD0qd4lSAqtMedlUE9T2FR/vpec7RT0FqQA7pAC2B609k2kpvBK+hFKbfHUim+Qc1FmVdCDOQcnB46UbmVtuaQxEdTV57rSzaR7IXWdE2spQbXbHXOePegCpvPrSbveq+DRg0rhYtCZh70GRT2warDNOHPXNO4WJ/MPrmmlvem7fT9acIx3NAEsIx87dCQM1cDQNtSU8DoydqgRQ0YHbI6VJdpbQyKtr5nl4z+865qX8SLS91kwhtSM/aTj6Vo6edPtrS5DXMZeVduG54/pVCFYZYg32Y56ZB4NWF0/fgLZSEnoAau6RlZvYhiGnx3MbyP5kKn54843D0zUt7Jo8yKLRTbuGJYl9wI9KdJod6QBFpc5JPerD+F9RjTe+jXKr3ORUc8e5XJLsQ6RLptnd+dPOJAEICkcc0U4aFcjk6XcYopqa6MTpyfRnW+GrYmLz9xIY9B0ra1G/W1tJNyDoRzms7Tg2nWojKM2D0RaTUHmvoWVLeXn+82K5ItKNj1eXnqXexzmnx/brwIpIJNddeQiy0GcSkuAuOeKx9B0iWKYTSqqY7EZNa+szb9Mkg8uVy4xwtEWki68nJqK2PP0kCpI2eACax3TJrau4JLa3YvEyBzgE96yZBs5HrjFdFBe62efiX7yRX8og8HFBMw6Nn8KsbaTbmuixy3Kwlb+NfypwlTp/OptgNEduZbe7lBXZGyKRnkk5oSbC5Vl+6WquibjmrZhBGOcelIIgvAqLFXIvL4pNlTkU3FOwEWylVRUmMUnfv+VIAAFKaOvalCn1AoAmtznip5oXlI4FRWxjjbLAufTOBWvHp1zPEkscTMjDIIrGq+WzN6S5k0PjedmSJLSFEOFDGTgD1NaIlTTtVikSNflGWw+QTVVdOvAm3yXPHpSPpV0RuEDce1YVJ81tTenBwvY7SLVbHyA/28bzg7GjwPpmnnWbV2k5gRSp2bZmJDds5XpXJxWtwEUGCXpj7tTC2lHS3l/75rkc5p6I6fZxfVm1/a0w6zWv4Of8KKyDbTMP+PaX8qKr2s+yJ9nHuyY+K3/594MfUUv/AAlMjD/j3iI9mFUWRMfdHX0qOKNPOPyL09KlJMq77ml/wlLoci2jHHPzU2TxXIwx5MJH+8DWbfIn2Q/IvX096RIoyq5jX8qOVA5O5X13Vm1D7PGY0QISfl7k4rAum/ewp6vmtLVVVZ0CgDjsKybn/j9h+lepRVqaPMrO9R3LBopT2pK3MCIqySrtJKKCcn6dKu2Vuv8Awj9xIwbznYOuMbdmec+9VJv9S/8Aumtu2Rf+EGL7Ru87Gcc421rRirv0ZFR6L1OeIYng4FL04zS9qbWJqBpKU0nekMQnBH5UfgKRuo+tLQAUfjRSigBycHNdVoXiabTtPFqIIZFRyQXAyAecfnmuUHWtzw8A0kwIB+UdfxrCuk6bN6DamrHQN41uWH/Hnaf98ij/AITa4GP9FtQPYCoERP7o6+lEiKDwo/KvMuj0dSb/AITa5PS2tvyFA8bXX/PtbfkKqlRt6DrSuABwBTuuwte5bHji8H/LC1/75orLmJGeTRRoLU//2Q=="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/vision/face/match" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_a_base64":"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","image_b_base64":"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"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Pencocokan wajah selesai",
  "data": {
    "match": true,
    "similarity": 0.7296,
    "multiple_faces": false,
    "model": "facenet512",
    "catatan": "Skor kemiripan wajah, bukan pembuktian identitas."
  }
}
```

### POST /vision/face/liveness

**Periksa keaslian wajah**

- URL: `POST https://api.apipedia.id/v1/vision/face/liveness`
- Auth: `Authorization: Bearer <token>`
- operationId: `checkFaceLiveness`

Penilaian liveness pasif dari satu gambar diam memakai model MiniFASNet. Menahan foto cetak dan pemutaran ulang layar, tetapi bukan pembuktian identitas - jangan dipakai sebagai satu-satunya pemeriksaan untuk keputusan berisiko tinggi.

Body JSON:

```json
{
  "image_base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAD6AMgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDD8IxvNKkSHkzLV6/0y/0SXUbKch45nMwZQccnpWd4RlMdyNpwfMBBFX5/EWrX8l/b6h5ixSIAoKYDYJwc0qMXKlNHoVZctamVtHONOuB6MKqWJ/4+T/tH+dWNJP8Aol0Peqmnn5bg/wC0f51wno9jRkP+lxH3p0Bx4mf3RaZJzPEaSE/8VP8AWMUimaV3zBOP9qtbwdLHDqcTygFfLbg1k3P+pnHvUuliVmjEH+sI4pwdmgrq9KS8j0CTUbqQDy0VU7ACq02n/bnE94+8HoorOsrvVIyA9sgAIHXtUt1qd/Cz7baNUyMSE9c+1ddpvqfN+6i7HptrPHJbGPbFvB2rT7zUdPszH5lzHGE+XaDk/kK5LUvEUhUww3QVW5dkB/Wua1K7UqoPzyEfLu+9VLQOS56L/a+htc+bBdRgsPm3AjP6Vq21pYtp81xA8UhYEkoQea8Vt5J03bQnIOGAPHTvW1pWqSxy7UuXjnJGSrYV/Y+/Wr5kyXTfc9YisjJCryHgjpTpbFpVC7sJjp61maL4nhubNFuwI3A4OeD/AIGtBvEOn+SHaZQScYz0q03YwcVfU4TxjGsGo+WqFAI+/euIc40+b/ers/Gl9Fe6mZIWDKIwMiuLl/5B0n+9Xnz+Nn0tD+BH0Ohumz4eY/7AotGzpVsKZcH/AIptv9wUmntu023rPodS+L5C6sf3F6P92mXvOjD6il1X/V3v0WkuOdIApg+pY8M38a+NbWykbC3NnsJ9GHI/ka0vGdp9luLqYfduIFOfUjg/0rhzdGz8a6fLniMxfkf/ANddr43uTJcyQ9kt1P5mumpG0EzyaUm8S/mc7fnOo6aPSMUUl7zqtiPSIUVzHqrdj/A1zbW19M9yRjYdnu1djH4m8P6rcagl0qiGOzSNXcZ3Nz0Hrmua8E6DZahpN3f3M+ySCTCAHHbvWrqXgbShNdS2V06/ZbVZiqtuJbBOPavTw8YctpdTwMTNuaa6HM6XxFeLnOKp6ef3Vwf9o/zq5poI+1ggglc4NUNPP7m4+p/nXmtWbR7ad7M03P7yI1HE2PE4/wCuYp55aKoEOPFC/wDXOpKZt3B3Rz/Wi0leGNJEOGA4NJMfkn+tP01dzwiiKu0XVdqbZNJf6sssccr+UXYKu7371Svrq9nuPJe4LmJtuVbjJ4P86u+IGt5L6HZM7tj94ew9qTSNOc25kk6s5+VuvXP8812TtDY+dp3na5q6bo1rDArtEHkYfMTzVuXTLKVh5lpGxByCV5q5ZqVABHI4xU8oBPSuBp73PTVlpYw20TTtxItEBJzxkVLbaHpBJV7NVLZ+YEitBtvBAH0qFnJBwvNKMmnuOUYtbFPVNOisPLngySWwUHG5f/rYrU0G5stRsw1xGhnjcxv8vUjofxFZd9K0kSZGdjZI7VmuNQt7+U6WsjPMVcgLnqP8a76FS+55mKpWQ3xrHHDqzpEAF8sHAFcZOf8AiXP/AL1dBrbag1w39phluAvIYY47Vztwf+Jew/2qxnrNnrUVahFeRvTtnwyx/wBgU3Smzp8H1ps5/wCKZb/cFM0Zs2MX1rLodSfvL0J9UPy3n0WnMN2mKPWotTPF5/urU8f/AB4J7CmhvqcdrUm3xIG/ubP0Ndbr8xl1S+JIIMceMemBXEa2+7WZG9hXStqJ1JWmZApESJx3x3rtrr92jx8NrWZNd/8AIYtPaIUUXf8AyGrcekQoriPXW7J9B0HV/sdndqCLCeQs3OPm6A4pviHQ/EGguxeZpIrld8zxMeOwBP0q5p/i2+8jTtGkhwkIMjM64LAcrioNQ8f3dxbrZ6haJcLJNuw3y/IDwPevdpqcXa21j5eq1JX82VbOG4tpriK6Rkl8oEhuvTis3Tz/AKPcfU/zrqvEGq2mr+Iri5sh+6+zKmexIH/165PTz/o1x9T/ADrxqqanK/c9+lLmhF+RqqciKqxOPFMf+5U8JyI6qyHHiiL/AHKzRs9kb8xyk/1q54es11C8htmfywyMd3pVF+Vn+tS6XK8U0LpnKg9KIfEh17+ylbsyG8sHtbyaIyecqMQzqCQPTPpW34XUvpwlk5fcwI9Dmm6W1nqmo3RVQsW1hJuOASQP/r1bsI0ggnS2bfCZWMZB6g46H65qqst4nl0oe7GXdC3PijTNIlMdwzySDqsa5I9qrw+ONMv5PLijmRz/AH4yKqTaVdbpGtpUtEIJyke9y3uaoWWi6mJ42NxNIUYF5JFAD/h2pPl5Slz81zqp9QigBkb5VxnJrnp/G9tDKYhaXDDOMqnB+lbesxmax8tVAYrjI9a5I6Tfhl/0u7iIbJeNs7h6YzWdPlb1NanNb3Tp9O1Ow1m3ZrVjvXh424OPpU1jePZai2yJndAEwq5JHX/CsTRrC6tdTjnf96pbBkZQjYPUH1q9qkMRu791Y+dtB4PQbR/hWkZqm+ZGTpOsuRmd4xvRfas8wOR5YArk7j/jyYe9bWs3X2q5kl2BO3Hf3rEuP+PI/Wqk7yuddOPLSUfI2Jz/AMUw3+4Ki0Js2iD3p1wf+KYf/cFQ6A2bdR7mo6G/216FvUz815/uipGfZpQPtgVDqR+a7/3RUd7Ls0y3UHl3A/SqgrtCqO0ZPyOO1ds6m5/2BWzpT7rY++KxNV5v294ga1dEYmJlPYiu6qr0jyMM7Vjcuj/xO4vaMUUlx/yHB7IKK889ldTor3X9FudX0KGziA8iLfKNuflxwCax9QvdI1rXoGVkRJr2MYkTBCA4IHtn+dWbjw3p+n/ECGytJGELQlnJbO3PYViT6A03iqOws2ZV+0Msb5BIxzn8692mo3dn0Pl5X5UdN4m0mw0jxDJFpxHkywbyobO1u49u1cbp5/0S4+p/nW7PpN5pGsXVveyiaYIGdwc9RmsDTz/oc/4/zry8SrVHrfY9vCu9KPzNa1OVSqc5x4mh/wByrVicqtUrk48Swf7tcy3Op7HQseJ6bAxES4coSMZFKf8AlvSWlwlsEmeFZlUElG6GlHcup/DZp2Nt9jka1yYnuYiBnueoP8617KMQQQxN8pVcMD65zWYPEyXeyRdISWSIZBXqmO9PstWGpTkm3aN5ATvJ+9jGB+H+NXWg78x5OHqe7yM3bpYUjMu8KvU57VU025a/jNxFGBbbiqO7csP72PSsjUhNdPHavIY4WBLnOMjIGP1q6zaU0KxieH9yNqhX5HtxWSjzHU5W0NC62BWR3UAd81kvdCxuES62mOU4WRTkKewPp9aozG1cgTXu9f7pbP8AKq811pyw/YzLEA44QnaT9M0uQfPbc6uJothUEHcM5B71mat5kc1xdquAYto92xVLR/PygkkLLuKhj3APWoLnxC8Uk0QiWWJpSwDnIH0qoQ5mZ1KnIrrcxbuR3jzJ94Lis2f/AI8vxrS1G5Fy7SqipkdF6Vmz/wDHn+NaP4jeD/dr0NS5P/FMv/uiq/h8/ul+pqa6P/FMv/uiqvh9vkH+8anoa/bXoX9QOWvP90VS1GT5LJPRXc/gtWb9vmvP90VVjTz9UhebK2sFu3mSYyEJHetaKvIyxMrQYugeG7bU9dVdY8yG3l08ywuCArYHOSfrXRa0PDl3Ek+ktCJ44YlYq/LEHBG2uI8S61JfSWtkCNlpAVDrxvz3+lU9EbGoW/u2DXbKPNBnkUnaqn5nUT/8hv8ACikkOdZb6UV5p76Cx07VYPF1zDePm6VCXO7OAf8A61Ztl9vsNQi1G2DSGKRyNpJztPP4VoaRrt3f+I7vUnCiSUYbjgjtn8K1NA8XQWDRxajbJ5UHnGJlTHBPQete+uZN6HyztyIrQa7e6s90dRO+ZwpDbMEYzxWBp5/0O4+p/nXdaTqWh3Wia/KzMJ55g0XmAZ5HygD2rhLD/j0ufq38683GK1S9rbHrYGV6aXa5qaccqPpVO7OPEkH+7VrTD8o+lU7048RQfSuJbnfL4UdET/rvpUaMVtwQMnaeKcTzL9KjjYrCCOuDSW5pU/hssWl1cWFo5hjbM3yhsUthdTQzRhztWN84Ix14NRm+cqmGIEZDBfeq13cvLKZBkBua62ro8CLtqdbcQxXihJFBQthwf7pGD+uKl0S1k8O2b2VrDby2ZdnQTDlc5yM/iaiXMaRtMMeZGpYehI5rQcEwbGOVI61yQbjoeg1GYTa/G7u0dhaxSMQxPXJH0Fc1dwNe3f8AaN8qSSxKUgUKAIx7D16VoPZRLKWUgn61Dd4jQMevRQOpPam5tgoRj0Kst2LW1faQNkexPr6/icmuelkEsucBVHQCun8R6IdK0fTZrlwt1dzOrx5+6NoIH4Y592rnmt5POMagNt7jsK6KcHCOvU5KlSM5adCtcgqnK7cjOKpzf8eg+tX9RlSRxszgLg/Ws+b/AI9B9axe56NP+GjRuz/xTT/7tVNAPy4/2qs3h/4pt/8AdqloJ/8AQqXRmv216F++PzXn+6Kn1XzdD0y4tdqML6xEpf8AiU9MfSql63N6f9kVgHULjUTqFxcOXYW6oCeyg8V0YddTjxstkQSWs17rKW8C7nkiXA/Cr2n6ayah5YRy8JyQoycg1N4eXf4jhc9rZTXQ6Pqv/CNePLqdoRMrgqUPHBwQRVSqvn5PI4nC0efzKbZ/tV85zznIorR16+stS8SPe2KlY5YgWUjBDd6K5WrM9mnLmipEGg2seiXOp2l4CswcLHgZJB6Gult7Hw3q3gyCGT5WgeRh5jAPvyeM+hNJ4vsHF7aTxw5LnYWHUnPArJ1rwJdDR9MltXLXNzIVmjVsoOp3Z9q9mMlLVuzPnZRtFWRzuhadNqmrR2cBEbO5HJ4GM1Wt4mt0vYH+9HI6n6hsVowW9x4a12NJTiS2lVsjuP8AOar3rpJqOqSRfcklZx+PNcWKrqrKy6HtUcFPDU4SbvzK5NpR+UfSqV+f+Kgg+lW9IPy/hVLUTjXoPpXGtzd/Cjod3zyfQUyJgI1z05zRn55P90VGrhbfcSAMHkmlFXaKqv8AdyuaVysKWuWUKTyuRyawrq4EUDu3QAmpJ9fZSpjAkdV2qzDIH0rB1G4knifc2WYV6Cp33PnVLlPedS0T7ZpNvcQKPNECb1H8Xyjp71ysh1G0jKovnx+nQj869ItE8rT4FcgbIlDE+wrz3XfE3h6fXYrbfMbYti4uY1DRc9P/AK5/Q1EsO56wRrSrqOkzHi1K9vbryLWzZ5ScYHQfWuz0Hw8bR1vL9xPd9VA+5F9PU+9bNjp+n2lspsYoxG6hg6YO8Hoc965H4leK/wCwdLGn2b4v71SAQeYo+hb6noPx9K0p0Ix1e5nVrynotjj/AB34pXWvFAht33WenZijI6O+fnb8wAPp71mxairBnWQRN0bPRq52DCjinyPmGTHpWjSa1IWmxuTjCKeoI6jkVWlP+ij61Rsbto14PUcj1rSU29zFsZvKPZgOPyrmnh3e8T0aWMio8s0Wbw/8U4/+7VHQTz+NXrrbLpDW0UiSSEYABxmqWkwy28pSVChzxnvXO4SjF3R2qrCc1yu5avnCrfE9Ntc/pFrJdWOoeWVX5RkscDGc1samk1wZ7eBS0kpAAFSCOPRPD3ECyTHPmB/XFaU5csbLdnFi5XqW7EPhiPzNeix0NqoyK6qbSZrfxTNqU2lSahZlAny84YDrWZ4Ws1F9ACU80wqTjv6DFd7HqcWkx2UVwJYWvJWAd1wob0PpWa96u2RU92mkcNeWltBftJZFzbkkZcYYNjJGPaiuk8b6SYbhNUiH7ufCyAdFfHX8aKmaalZnfhpXpI19Ymt9QsLaaN1dGkDoc9ay/Cl1dNrV7Y3BIigJMSdgGbJNYfhxzd6bZQoxbyC7MM8Cu5SK3t7Gz1HaqOxETsB1B6frXXTqaPzOOvRUH7O+zMHx/oyvYRavEvzxt5cvuD0P5154o2rce65r23VbMX/hm/tyMloiR9QMivEz0m/3K5qitO/c9bDVfaYFxf2X+DLOjnI/Cq9/BI2rxzAARoOWJqvHePbQAIcFsDNRy3DudzNkmtqdC/vM8uti7e5FGnPquGJjGMjHNZlxeSSHLEsPSoSx9adgdvzNdcYqOiR586k5u8ncf5uFBHPFLZw/a9TsrZj/AK+5jjP4sB/WoTzVvRQ7+JdJWNdzfbImC+uGB/pTZmeu/EnxHcWVtFommOEuZ8PcP18uIHp/wLH5A+teZ35ENw7SR7XLKxt1yEdcfeOD1r0Dxs1lYzzyyFI7u8VlWRhnYo6u2c+mAP8ACvN9HkF2qO6qu6Zsbzyc4C4+gau2haMDnndu53vw01y9ttNvbXUkcaZbb5bW4YYCqMlk57dx+Neba/rE3iDXLnU5sjzn+RT/AAIOFX8q7zxzqMWj+GYdGtCFacbDjrsHLH8eB+JrzPFck9zaHccjhRgjimNcrkxqNxPBA7fWg9KYEVcbRjceg71BoWIVCqB7VOJdvHWq/SlzmqEWluWB4OKvW2osnEnK+4rHBA5PSmmVnbqaL9wWjujpdOlMmsedE6lSh3IfvfhVPxAWkit4gCVeZs+5rMSYwSLIkuHU5HNbeqkP/YgAK+ZcbsEep9a5JU1GpdHRFyqNykbmgSHSfFdjcXCqE8gD95wBxit7UtOj1x5bOS+aUMxeEmUNs9xj0qLRJA/ilXMaTpDHIGU845HSuyfT9LgkN7aWkUckg+Z1XGRWUqbkt7FTmlM57VFnfwLLBduGnswEkb+9j7rfiKKi1/U4r+y1L7GymCOERSnP3nzkYHtRUVHdnZhG1BnK2jz6DoEEuBDL9pMUpI/hJ5rtfEUflfDqYxS7jHskR/8AgQNcd4/uLiG8GniNRBJiUNjknoadaeJVk8B6ho92/wC8RB5BP8Qz0/Cq5krxOyOGqVYxrpdU36XPR/DN6up6DBcZ3ebGN31715T4s0k6Pr95bD/VuvmR/Q9q6b4W6ucTaTI3bzIv6imfFmNIZrG6x80iNH9ehqkvaRTOao3hK06b2f8Aw6PL5m/eRgdN38hTieajk++h/wBr+lLn5q7keMxx6VITUR6U8c80CDtW14IiE/jvSVbG1JTIxPYKMn+VY3atzwfHKmoajfwkLJa2DiNicYeQhAfyLH8KaV3YG7I0fEeoNr+pXNwAGa7YNBmTAiRegOeMkD9ayyq2enGUMxiMhMQ3BSku1ctgds1fuYlk8hB5n2aUkwJuUsZOBlvYmqmvzvHFImMXEhKXI2rtHPAXH0r1nFQj6HEm5Oxj6nqdxq1ytxcHLLGqAZ6Y6/rk1WhdI5o3kj81FcFo843gHkZ7ZptIa8hncjW1nW9N1CxSCz0C206RZNzTRvuZhg/L0Hc/pWQi7fmI+Y/pSKMtnsv6mlBySfSklYbdx3cCmvJyEXqf0prv5cZc1DGH2bv425J9Pai4iZnUcE8UCSM8GXYPYVF5APLc/WlCxRkfLk+gFAy7FFp7jJkfdjqRwa7DxVo/n6RpiRuYXhhVwT64Ga5CPz3geXYpEK79gHXFdXeatrWu2ltNHoUy26pjzAQQR61jW5rXib0HFNqR0PgOznjhW6nQIXjIHq/PLfStTWtXmt9DktoR/pEr+XGfrUejOUubOS8m8pmiAEEfPydh9SazNZkjmE1lJJJEUlJIAyetYupFU7Nl1aM/aqUVoykIbZrS5mtbFljFuUbe/wDy0HVh60VbvWM2lFYxiNYztUUVwqo5NnfShyxsbnijRU13Q2eNQbiLLxMO/t+Nea2GnvqMzWoysm1iBjuO1eoeDJ3uPC1m8jFm24JP1rP1Tw8NO8V2+qWy4guCwkAHCtjr+NdU4c1pHVgcW6HPQk+9jivCd4NM8S2c0p2Ksmx/bPFa/wAWtXhudZtdPjOfskRaQ/7T4IH5AfnWZ4q0xtO1h2CkRXH7xCP1rntVvJ9R1CS4uX8yUhVJ9lUAfoK0w2knFkZwozp068euhRdgGQerCnEfPVeT5GGcgZH0q0R8+a7T50TvTl6U0daXpn60wHZ4rX8POCLu353yvDsULnew3kL+dY4NWtHm26xDbs6xLMcmVs/uyAcHj6mtKX8REz+FnSS4eWSRhhZSBdP5PEBLdF/KsHW5S1xFEUCiOPhguC4JyCfwrZd0AWVokwu1WtssDLwTvNcxPKZ52kYk5PGTngcAflXbipWhbuc9FXlciIpjZyAOp6U89KYDgFz1PT6V5p1iMQAEXoKUL8oHrUYYA5OacZsghVI9zQBXvXyVQetSiUfdRC2O/aqVwxaYCrkMYRAW/DNSnqN7Eg3NycClEscbdNx9BUbAN1bj0FKinPypgflmmBfhluJowiIEjd1Qgnrk45Nez6PaJFpa2qgKgTaNvQcV4vaNGWxcXCovZevPqcV6x4F16HWdMXzJYxdDOYlP3VHGaiorocTE1S/uvCpSVrUv5UuTPnJ2ngAVhSeJ7KbzZWuNzyEtz1ya6j4oK39gNs5zIucfWvI7Wwu7sSPb27yrDy5UfdFcyw8J7vY6frEzub7xfaJbi2sx5rsQC3Ycc0VxYgmgkQywyR5PG5SM0UnhoR0RtTrSktT1H4c63sLaNPx1aLPr3FeiXqB7CTIzgZFeQ+Is6N4weazARkKyAdsnrXpVpr0GreE57+3I3pCxdO6sB0pUpbxfQ68fQb5cRBaSt95z/wAQIFk8EpdCMGSKZAH7oGyD/QfjXkbBkbeoOT1A6GvRfH+ted4f0qwifC3I+0ygdwOFH5lj+Feesx7DiuyC0uePVk78r6FO5Jdcngjmr3UA+tUJ4WfcwLDuQeauRPuiQ+oFUtzNj6Ruv4U12ORSsflB96oQA81JZSRx6zbtMhdPmG0NjnBx+tQqecUsUjxajbuhxzg8diMH9KqDtJMUtmdPfXE0EM5M267jGGmEgIKEABR781zQ4Fa2sOyW0cUYP2VpGaEsAGYd84rIBGK3xMrzsZ0VaI1vmbZ26n6U1yN2T096Bym/oW5/DtTDwc8VymwFh6E0xm44GKC49ajkcAUhpEcKCS4Ytzt7VbKrnlck9Bmtrwh4Ql1djf3zm101Tl5TgFlHXbn+f86r3trDqOvTweHLOeWJ32wxjLkD1z6fWs4zWxbhK1zMaUg7I1Ge57CnJCzcuSanuLGbTLuS0vImiuIjh0ccg01fOY4XCitFqQ9AmgUW8hUH7hIqHS9XvtIeWSxnMLyoY2IHY1qWNsJ7qK3kuPLWVgjSYztB4zVnxh4Wh0W+I06RriDYGfauQmeOv4UOwI6XwJq17rs10+pSJcBERER8YG3viorq3dnvX07yl/e5lCN2z6V53a3M9q7yW8zxOBjcjYNRx3M8bFkmdWY5YhjyfeuJ4V+1c77nR7VcijY9M8W3stt4dKXLTH7YUEG5EG0DqCOoorgLzU73Umja9uHmMa7ULdhRXUo2JhI9R+IGjBo4tYhHI+SX6djXK6ZrVzpK3KwnMdxEUkQng5HWvT9ECa14VtkvQJFuIQH968x8S6LN4fvpbeUExn/VP/eH+NcE4NyUo9T6PB4mCoTo1deW/wA0jGu7ya9eMyvu8qNYk9lHQfz/ADqqwOfSlwc8UP8Ad5r00klY+Wk3JtsrSyFSVFNtpcRbT1UmmzuFPUHFa+g+D9T1hRc70tbeTlXkySw9QB/9as5TUdWVGDlokZzSgrmnqRJF6ZrodQ8AXlpF/ot5Hct/dZdmf1Nc75U1nI1tdxNDKv8AC4ohUjPZhOnKG6EU5IPrTZ2KzQuBuKuDj15pFYBiM+9Nu2/dhlOCDkGtLkGvrEga5B4DFctGBgRkk/KP0rPfkBP7xx+Hen3cvm3TMW3E9WznPvUSnc7N2X5R/WtKkuabZEFaKElcjgVUcsT1NTSsMmq7Ng1izRCbjXWeEPC8Wp7dS1NW+xqx2R4/1pHr7dves7wv4an166DvmOzVsSSd2/2V9/ftXq7mCzghtLaAFlXCIo+WJB/n8a5a1W3uo7KFK/vPYhm0W31BN+oSeVZIuEtY2KrgdN2Ov06VFYXSwFbTQ9PUDcV80YjjH1bv+GaeZrC0U3Gp3Bkcc7ZW+Vf+A9P51Xe/1vU7e5m0XTxDbxqW8+7Plh8Doinkn8hXLG7Ot2RyHjuxubLxEs11dR3T3UQYNGpAQL8u3H9e9YClj14FLc30+oTG6uJTI7/xv/IVGrwg5Z2Y+1enBOMUmeXUkpSbRO7FIHYHBCHkfSpdE8QmysLzT7ku0V6AplzlovcVd0PS0164nt3YxRrAzFvfoP1P6Vzt9ZTadeyWs4w6Hr6j1p3TfKSk7XNvxLpFnBcouiq9xEsCmV0XIB9T6da577JcKoZoJFUEZJUgDPSux8K+MNO0XQW067s3maS53M6tjavB59fpS6746tL/AEgWNtp6K63BcO3PGeD/APWoTYFjQvh7NILO+1V1WxuDw0bDgbchifTPFFc9e+J9V1DSodMuLlmt4X3Kq/Lx6cdhRSabKR6p8PdTaWwfTJl2y2bY+oqp8WbqJdO0612gzSStJnuFUY/Ut+lc9Hq0mh+Jlvo+Y5FUyKP41PWo/iHqyap4ijaBw8EVtGEI6fN8x/mPyrjwlT2kEz0sxounL2i2l/TOTeRYvvHn0qvNdqylNrEEdqmleNRyMtUAYscrEPriu88krW1v9pvIoN3EjAZ717VpUSR20URTCoAMemK8jWDOH2qrLypA6GvRvCesx6jZbHyJ4fllU/zHsa4cVCWj6HdhZx1XU69IUZAygY7DFUdWsoLuWKzmsVdHUvLcSxBkgjAJZsnvgHA+lX4JomIQFT8uBzjBq14y2WPw71J4hh3ttpfudxAPP41NGkpe8wr1nF8qPC9SuLe91Ca4tbWO1t2bEMKLgIg4XPqcdT3OaqPGjjay5FO4pK9A4RvkqeRn86YVKJhTkDrmpgaZKCPmHagBjWysud5Jx2qsbSVuVIx7mrIYg5X/APVUisCMjp6elK1x3Oz0PxJpGj6ZBZoZtyLhm8rqep/Wrdx4ysmi8uykKySkAvKrAL7muCzSbqweHg3c6FiZpWO9ttX8N6aPtc8rX99/CSN20+oHQfzrE17xzqeqKbeMfZ7RhgiM/M49z/QVzxfIqPOVwacaMYkyrzl5DwFdd6/MKTOTgLTS/lsGTgN1+tP80yYOABWxib3hdbr7Xst43bziIyQOBk960/GWimWyMioDcWpOSvO5e9bvgKe304Wdk0RNxcI0rkjoW5H/AI6BTba8gfUbiKYjcXdtjemea5Kl+fmXQ1i9LHka/cb3NRr94fWtfxBaw2er3MUAwm7cB6ZrHX7w+tdSd7MgsiigUVTBHZSxPfeHrS9RdxiGyQjsPWsG8UiQP/eFdN4KvoprKTTJBkkkgHuD1rO8SaU+lThPvRM2Y29vSvBwlX2eJdF+dvQ+hrtVcC091qjCEYzkjmpB7CkJFRNJK3EQx6uele/sfNEzEKMu4UUtpf3Njci6sJWSQDBf+Ej0Oeoqkyxo2ZGM0nv0FSJukOW6elS9dGNaao2l8banZN5mIfNzkMoIrqLv4i/8JR4N1SzuYkguVji2AHiQbxuwPbivNbld9wWk+WNAPxpbZ90rPjCgYA9KiMVF6FSblqy2TTdxXnqKN6etIGUnhga0JHLIp70Fx1qMop5zim528A5FACnCnI5U/pSk7W3DoeopmSOV5HcUAhhgflSGTBgRxyKM54qD5kORyPSnhw/saAHHjmjIC0m71prsMUALnKEde9W7COBrqFZ95t2cGTYMtt749+1Z+5iwVDg+tbejF1leWO7treQjaPOO3I9qlsaR3fhfUrXUvFcksFu0QWJmRGXaBwABVLxRYXcHirRFjgEDXMrKSGB3ZNR201tbOJk1O08zbgnzapatfWk00d1JeJcXFqC0WyQnaax9nK5XNEwPGto1h4murZzkrg/mK5xfvD612Eut2F3M09zplvLK/wB5nBJP603+0dH/AOgNaf8AfH/162WiRN/I5ntRW1qNxYXVuYrTTIYZCch0JBH60VV0CJfCz7NatDuwvmDP0rttZSDxDoF4loRJNAxeNR1yp5H5ZrndM0O2ln2wecNp+8G6V1OlaFBpLSXCTy5ZTuVjwa8PEUZVXGpT+KOq/wAj24yhGMoze55a0hQ9M1E7zS8D5R7V1dxpGlSzOym4d3csfn7k1PH4Pt5Ld52mmgRRnJwa9ZVk9zzZYWcVc4+OEJ161I8m0YUZNXpNNYMwhuEkAP8AECp/rWaZ2U4Eag+ua1UovY5pQlHdEZtpJn3ScD0qd4lSAqtMedlUE9T2FR/vpec7RT0FqQA7pAC2B609k2kpvBK+hFKbfHUim+Qc1FmVdCDOQcnB46UbmVtuaQxEdTV57rSzaR7IXWdE2spQbXbHXOePegCpvPrSbveq+DRg0rhYtCZh70GRT2warDNOHPXNO4WJ/MPrmmlvem7fT9acIx3NAEsIx87dCQM1cDQNtSU8DoydqgRQ0YHbI6VJdpbQyKtr5nl4z+865qX8SLS91kwhtSM/aTj6Vo6edPtrS5DXMZeVduG54/pVCFYZYg32Y56ZB4NWF0/fgLZSEnoAau6RlZvYhiGnx3MbyP5kKn54843D0zUt7Jo8yKLRTbuGJYl9wI9KdJod6QBFpc5JPerD+F9RjTe+jXKr3ORUc8e5XJLsQ6RLptnd+dPOJAEICkcc0U4aFcjk6XcYopqa6MTpyfRnW+GrYmLz9xIY9B0ra1G/W1tJNyDoRzms7Tg2nWojKM2D0RaTUHmvoWVLeXn+82K5ItKNj1eXnqXexzmnx/brwIpIJNddeQiy0GcSkuAuOeKx9B0iWKYTSqqY7EZNa+szb9Mkg8uVy4xwtEWki68nJqK2PP0kCpI2eACax3TJrau4JLa3YvEyBzgE96yZBs5HrjFdFBe62efiX7yRX8og8HFBMw6Nn8KsbaTbmuixy3Kwlb+NfypwlTp/OptgNEduZbe7lBXZGyKRnkk5oSbC5Vl+6WquibjmrZhBGOcelIIgvAqLFXIvL4pNlTkU3FOwEWylVRUmMUnfv+VIAAFKaOvalCn1AoAmtznip5oXlI4FRWxjjbLAufTOBWvHp1zPEkscTMjDIIrGq+WzN6S5k0PjedmSJLSFEOFDGTgD1NaIlTTtVikSNflGWw+QTVVdOvAm3yXPHpSPpV0RuEDce1YVJ81tTenBwvY7SLVbHyA/28bzg7GjwPpmnnWbV2k5gRSp2bZmJDds5XpXJxWtwEUGCXpj7tTC2lHS3l/75rkc5p6I6fZxfVm1/a0w6zWv4Of8KKyDbTMP+PaX8qKr2s+yJ9nHuyY+K3/594MfUUv/AAlMjD/j3iI9mFUWRMfdHX0qOKNPOPyL09KlJMq77ml/wlLoci2jHHPzU2TxXIwx5MJH+8DWbfIn2Q/IvX096RIoyq5jX8qOVA5O5X13Vm1D7PGY0QISfl7k4rAum/ewp6vmtLVVVZ0CgDjsKybn/j9h+lepRVqaPMrO9R3LBopT2pK3MCIqySrtJKKCcn6dKu2Vuv8Awj9xIwbznYOuMbdmec+9VJv9S/8Aumtu2Rf+EGL7Ru87Gcc421rRirv0ZFR6L1OeIYng4FL04zS9qbWJqBpKU0nekMQnBH5UfgKRuo+tLQAUfjRSigBycHNdVoXiabTtPFqIIZFRyQXAyAecfnmuUHWtzw8A0kwIB+UdfxrCuk6bN6DamrHQN41uWH/Hnaf98ij/AITa4GP9FtQPYCoERP7o6+lEiKDwo/KvMuj0dSb/AITa5PS2tvyFA8bXX/PtbfkKqlRt6DrSuABwBTuuwte5bHji8H/LC1/75orLmJGeTRRoLU//2Q=="
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/vision/face/liveness" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"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"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Deteksi liveness selesai",
  "data": {
    "real": true,
    "score": 0.9975,
    "realness": 0.9975,
    "fakeness": 0.0025,
    "model": "minifasnet",
    "catatan": "Penilaian pasif dari satu gambar diam. Menahan foto cetak dan pemutaran ulang layar, bukan pembuktian identitas."
  }
}
```

## Gempa

Informasi gempa bumi dari umpan terbuka BMKG

### GET /gempa/terkini

**Gempa terakhir yang tercatat BMKG**

- URL: `GET https://api.apipedia.id/v1/gempa/terkini`
- Auth: `Authorization: Bearer <token>`
- operationId: `getGempaTerkini`

Meneruskan umpan autogempa.json milik BMKG. Magnitudo dan kedalaman dikembalikan sebagai angka, koordinat sebagai objek {lat, lon}, dan shakemap sebagai URL penuh. Respons upstream disimpan lima menit di memori. Data: BMKG.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/gempa/terkini" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Data gempa terkini berhasil diambil — Data: BMKG",
  "data": {
    "gempa": {
      "tanggal": "2026-09-15T16:12:48+00:00",
      "tanggal_lokal": "15 Sep 2026 23:12:48 WIB",
      "magnitude": 4.2,
      "kedalaman_km": 34,
      "koordinat": {
        "lat": -4.36,
        "lon": 101.99
      },
      "lintang": "4.36 LS",
      "bujur": "101.99 BT",
      "wilayah": "Pusat gempa berada di laut 72 km barat daya Seluma",
      "potensi": "Gempa ini dirasakan untuk diteruskan pada masyarakat",
      "dirasakan": "II-III Bengkulu",
      "shakemap": "https://data.bmkg.go.id/DataMKG/TEWS/20260915231248.mmi.jpg"
    },
    "source": "BMKG",
    "cached": false
  }
}
```

### GET /gempa/m5

**Daftar gempa magnitudo 5 ke atas**

- URL: `GET https://api.apipedia.id/v1/gempa/m5`
- Auth: `Authorization: Bearer <token>`
- operationId: `listGempaM5`

Meneruskan umpan gempaterkini.json milik BMKG, berisi gempa terakhir bermagnitudo 5 ke atas. Data: BMKG.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/gempa/m5" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar gempa magnitudo 5 ke atas berhasil diambil — Data: BMKG",
  "data": {
    "jumlah": 15,
    "gempa": [
      {
        "tanggal": "2026-09-14T10:58:04+00:00",
        "tanggal_lokal": "14 Sep 2026 17:58:04 WIB",
        "magnitude": 6.2,
        "kedalaman_km": 145,
        "koordinat": {
          "lat": 2.52,
          "lon": 128.09
        },
        "lintang": "2.52 LU",
        "bujur": "128.09 BT",
        "wilayah": "42 km TimurLaut PULAUDOI-MALUT",
        "potensi": "Tidak berpotensi tsunami",
        "dirasakan": null,
        "shakemap": null
      }
    ],
    "source": "BMKG",
    "cached": true
  }
}
```

### GET /gempa/dirasakan

**Daftar gempa yang dirasakan masyarakat**

- URL: `GET https://api.apipedia.id/v1/gempa/dirasakan`
- Auth: `Authorization: Bearer <token>`
- operationId: `listGempaDirasakan`

Meneruskan umpan gempadirasakan.json milik BMKG. Kolom dirasakan memuat skala MMI beserta daerah yang melaporkan. Data: BMKG.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/gempa/dirasakan" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar gempa dirasakan berhasil diambil — Data: BMKG",
  "data": {
    "jumlah": 15,
    "gempa": [
      {
        "tanggal": "2026-09-15T16:12:48+00:00",
        "tanggal_lokal": "15 Sep 2026 23:12:48 WIB",
        "magnitude": 4.2,
        "kedalaman_km": 34,
        "koordinat": {
          "lat": -4.36,
          "lon": 101.99
        },
        "lintang": "4.36 LS",
        "bujur": "101.99 BT",
        "wilayah": "Pusat gempa berada di laut 72 km barat daya Seluma",
        "potensi": null,
        "dirasakan": "II-III Bengkulu",
        "shakemap": null
      }
    ],
    "source": "BMKG",
    "cached": true
  }
}
```

## Cuaca

Prakiraan cuaca tingkat kelurahan/desa dari API publik BMKG

### GET /cuaca

**Prakiraan cuaca satu kelurahan/desa**

- URL: `GET https://api.apipedia.id/v1/cuaca`
- Auth: `Authorization: Bearer <token>`
- operationId: `getCuaca`

Meneruskan API prakiraan cuaca publik BMKG. BMKG hanya melayani level adm4 (kelurahan/desa), jadi kode kecamatan tidak bisa dipakai di sini dan tidak ditebak otomatis. Tiap kode disimpan 30 menit di memori. Data: BMKG.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `adm4` | query | string | ya | Kode kelurahan/desa dengan pola dd.dd.dd.dddd. contoh: 31.71.03.1001 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/cuaca?adm4=31.71.03.1001" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Prakiraan cuaca berhasil diambil — Data: BMKG",
  "data": {
    "lokasi": {
      "adm4": "31.71.03.1001",
      "provinsi": "DKI Jakarta",
      "kabupaten_kota": "Kota Adm. Jakarta Pusat",
      "kecamatan": "Kemayoran",
      "desa": "Kemayoran",
      "lat": -6.1647214778,
      "lon": 106.8453837867,
      "zona_waktu": "Asia/Jakarta"
    },
    "jumlah": 24,
    "prakiraan": [
      {
        "waktu": "2026-09-16 00:00:00",
        "waktu_utc": "2026-09-15 17:00:00",
        "suhu_c": 28,
        "kelembapan": 65,
        "cuaca_desc": "Cerah",
        "arah_angin": "E",
        "kecepatan_angin": 2.2,
        "jarak_pandang": "> 10 km"
      }
    ],
    "source": "BMKG",
    "cached": false
  }
}
```

## Kode Pos

Pencarian kode pos di atas data wilayah Kemendagri

### GET /kodepos/cari

**Cari kode pos dari nama tempat**

- URL: `GET https://api.apipedia.id/v1/kodepos/cari`
- Auth: `Authorization: Bearer <token>`
- operationId: `cariKodePos`

Mencocokkan nama kecamatan maupun nama kabupaten/kota. Lima digit angka diperlakukan sebagai kode pos. Kode pos melekat pada kecamatan, bukan kelurahan atau ruas jalan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `q` | query | string | ya | Nama tempat atau kode pos, minimal 2 karakter. contoh: menteng |
| `limit` | query | integer | tidak | Jumlah hasil maksimum, 1 sampai 100. Default 20. default: 20; contoh: 20 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kodepos/cari?q=menteng&limit=20" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ditemukan 1 wilayah yang cocok",
  "data": {
    "q": "menteng",
    "jumlah": 1,
    "hasil": [
      {
        "kecamatan": {
          "kode": "31.71.06",
          "nama": "Menteng"
        },
        "kabupaten": {
          "kode": "31.71",
          "nama": "Kota Administrasi Jakarta Pusat"
        },
        "provinsi": {
          "kode": "31",
          "nama": "Daerah Khusus Ibukota Jakarta"
        },
        "kodepos": [
          "10310",
          "10320",
          "10330",
          "10340",
          "10350"
        ]
      }
    ],
    "catatan": "Kode pos melekat pada kecamatan mengikuti data wilayah Kemendagri, bukan pada kelurahan atau ruas jalan. Satu kecamatan bisa memiliki lebih dari satu kode pos."
  }
}
```

### GET /kodepos/{kodepos}

**Semua kecamatan yang memakai satu kode pos**

- URL: `GET https://api.apipedia.id/v1/kodepos/{kodepos}`
- Auth: `Authorization: Bearer <token>`
- operationId: `detailKodePos`

Kode pos harus 5 digit (400 kalau tidak). Kode pos yang tidak ada pada data dijawab 404.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kodepos` | path | string | ya | Kode pos lima digit. contoh: 40115 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/kodepos/40115" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kode pos 40115 berhasil ditemukan",
  "data": {
    "kodepos": "40115",
    "jumlah": 1,
    "hasil": [
      {
        "kecamatan": {
          "kode": "32.73.09",
          "nama": "Bandung Wetan"
        },
        "kabupaten": {
          "kode": "32.73",
          "nama": "Kota Bandung"
        },
        "provinsi": {
          "kode": "32",
          "nama": "Jawa Barat"
        },
        "kodepos": [
          "40114",
          "40115",
          "40116"
        ]
      }
    ],
    "catatan": "Kode pos melekat pada kecamatan mengikuti data wilayah Kemendagri, bukan pada kelurahan atau ruas jalan. Satu kecamatan bisa memiliki lebih dari satu kode pos."
  }
}
```

## Google Calendar

Aksi kalender lewat akun Google yang terhubung di halaman Koneksi

### GET /toolkits/googlecalendar/events

**Daftar event Google Calendar**

- URL: `GET https://api.apipedia.id/v1/toolkits/googlecalendar/events`
- Auth: `Authorization: Bearer <token>`
- operationId: `listCalendarEvents`

Dieksekusi lewat tool Composio `GOOGLECALENDAR_EVENTS_LIST` dengan `singleEvents: true` dan urutan `startTime`. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `limit` | query | integer | tidak | Jumlah event, 1-100. Nilai di luar rentang jatuh ke 10. default: 10; contoh: 10 |
| `calendar_id` | query | string | tidak | Id kalender. Default primary. default: primary; contoh: primary |
| `time_min` | query | string | tidak | Batas bawah waktu event, RFC 3339. contoh: 2026-09-16T00:00:00Z |
| `time_max` | query | string | tidak | Batas atas waktu event, RFC 3339. contoh: 2026-09-30T00:00:00Z |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/googlecalendar/events?limit=10&calendar_id=primary&time_min=2026-09-16T00%3A00%3A00Z&time_max=2026-09-30T00%3A00%3A00Z" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar event kalender berhasil diambil",
  "data": {
    "tool": "GOOGLECALENDAR_EVENTS_LIST",
    "result": {
      "items": [
        {
          "id": "6q8k2v1n0r3s4t5u",
          "summary": "Rapat mingguan tim",
          "start": {
            "dateTime": "2026-09-20T09:00:00+07:00"
          },
          "end": {
            "dateTime": "2026-09-20T10:00:00+07:00"
          }
        }
      ]
    }
  }
}
```

### POST /toolkits/googlecalendar/events

**Buat event di Google Calendar**

- URL: `POST https://api.apipedia.id/v1/toolkits/googlecalendar/events`
- Auth: `Authorization: Bearer <token>`
- operationId: `createCalendarEvent`

Dieksekusi lewat tool Composio `GOOGLECALENDAR_CREATE_EVENT`. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).
Field `start_datetime` (wajib, alias `start`) memakai ISO 8601 tanpa zona, misalnya 2026-09-20T09:00:00; zona waktunya diatur lewat `timezone`. Field opsional: `summary`, `description`, `location`, `timezone`, `calendar_id`, `event_duration_hour`, `event_duration_minutes`, `attendees` (array email), `create_meeting_room` (true untuk menyertakan tautan Meet). Efek samping nyata: undangan terkirim ke peserta.

Body JSON:

```json
{
  "start_datetime": "2026-09-20T09:00:00",
  "summary": "Rapat mingguan tim",
  "timezone": "Asia/Jakarta",
  "event_duration_hour": 1,
  "attendees": [
    "rekan@contoh.com"
  ],
  "create_meeting_room": true
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/googlecalendar/events" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"start_datetime":"2026-09-20T09:00:00","summary":"Rapat mingguan tim","timezone":"Asia/Jakarta","event_duration_hour":1,"attendees":["rekan@contoh.com"],"create_meeting_room":true}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Event kalender berhasil dibuat",
  "data": {
    "tool": "GOOGLECALENDAR_CREATE_EVENT",
    "result": {
      "id": "6q8k2v1n0r3s4t5u",
      "status": "confirmed",
      "summary": "Rapat mingguan tim",
      "htmlLink": "https://www.google.com/calendar/event?eid=NnE4azJ2",
      "start": {
        "dateTime": "2026-09-20T09:00:00+07:00",
        "timeZone": "Asia/Jakarta"
      },
      "end": {
        "dateTime": "2026-09-20T10:00:00+07:00",
        "timeZone": "Asia/Jakarta"
      },
      "hangoutLink": "https://meet.google.com/abc-mnop-xyz"
    }
  }
}
```

## Google Workspace

Docs, Sheets, Drive, dan Meet lewat akun Google yang terhubung

### POST /toolkits/googledocs/documents

**Buat dokumen Google Docs**

- URL: `POST https://api.apipedia.id/v1/toolkits/googledocs/documents`
- Auth: `Authorization: Bearer <token>`
- operationId: `createGoogleDoc`

Dieksekusi lewat tool Composio `GOOGLEDOCS_CREATE_DOCUMENT`. Field `title` dan `text` (alias `content`) wajib. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).

Body JSON:

```json
{
  "title": "Notulen Rapat",
  "text": "Agenda:\n1. Evaluasi kuartal"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/googledocs/documents" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Notulen Rapat","text":"Agenda:\n1. Evaluasi kuartal"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Dokumen Google Docs berhasil dibuat",
  "data": {
    "tool": "GOOGLEDOCS_CREATE_DOCUMENT",
    "result": {
      "documentId": "1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789",
      "title": "Notulen Rapat"
    }
  }
}
```

### POST /toolkits/googlesheets/spreadsheets

**Buat spreadsheet Google Sheets**

- URL: `POST https://api.apipedia.id/v1/toolkits/googlesheets/spreadsheets`
- Auth: `Authorization: Bearer <token>`
- operationId: `createGoogleSheet`

Dieksekusi lewat tool Composio `GOOGLESHEETS_CREATE_GOOGLE_SHEET1`. Field `title` wajib. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).

Body JSON:

```json
{
  "title": "Rekap Penjualan 2026"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/googlesheets/spreadsheets" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Rekap Penjualan 2026"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Spreadsheet berhasil dibuat",
  "data": {
    "tool": "GOOGLESHEETS_CREATE_GOOGLE_SHEET1",
    "result": {
      "spreadsheetId": "1ZyXwVuTsRqPoNmLkJiHgFeDcBa9876543210",
      "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1ZyXwVuTsRqPoNmLkJiHgFeDcBa9876543210/edit",
      "properties": {
        "title": "Rekap Penjualan 2026"
      }
    }
  }
}
```

### GET /toolkits/googledrive/files

**Daftar berkas di Google Drive**

- URL: `GET https://api.apipedia.id/v1/toolkits/googledrive/files`
- Auth: `Authorization: Bearer <token>`
- operationId: `listDriveFiles`

Dieksekusi lewat tool Composio `GOOGLEDRIVE_LIST_FILES`. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).
Tanpa `q`, `folder_id`, atau `search`, daftar dibatasi ke folder akar dan berkas yang belum dibuang ke Sampah - tanpa itu Google Drive memulangkan SELURUH isi Drive secara datar, termasuk isi tiap subfolder.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `limit` | query | integer | tidak | Jumlah berkas per halaman, 1-1000. Nilai di luar rentang jatuh ke 100. default: 100; contoh: 100 |
| `folder_id` | query | string | tidak | Id folder yang isinya ditampilkan. Kosong = folder akar (`root`). contoh: root |
| `search` | query | string | tidak | Cari berkas berdasarkan nama di seluruh Drive (mengabaikan folder_id). contoh: kontrak |
| `page_token` | query | string | tidak | Kursor halaman berikutnya dari `nextPageToken` respons sebelumnya. |
| `q` | query | string | tidak | Kueri mentah sintaks Google Drive; menimpa folder_id dan search. contoh: mimeType='application/pdf' |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/googledrive/files?limit=100&folder_id=root&search=kontrak&q=mimeType%3D'application%2Fpdf'" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar berkas Google Drive berhasil diambil",
  "data": {
    "tool": "GOOGLEDRIVE_LIST_FILES",
    "result": {
      "files": [
        {
          "id": "1QwErTyUiOpAsDfGhJkL",
          "name": "Kontrak 2026.pdf",
          "mimeType": "application/pdf"
        }
      ],
      "nextPageToken": null
    }
  }
}
```

### POST /toolkits/googlemeet/spaces

**Buat ruang pertemuan Google Meet**

- URL: `POST https://api.apipedia.id/v1/toolkits/googlemeet/spaces`
- Auth: `Authorization: Bearer <token>`
- operationId: `createMeetSpace`

Dieksekusi lewat tool Composio `GOOGLEMEET_CREATE_MEET`. Body opsional; `access_type` bisa OPEN, TRUSTED, atau RESTRICTED. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).

Body JSON (opsional):

```json
{
  "access_type": "TRUSTED"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/googlemeet/spaces" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"access_type":"TRUSTED"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ruang Google Meet berhasil dibuat",
  "data": {
    "tool": "GOOGLEMEET_CREATE_MEET",
    "result": {
      "name": "spaces/jQCFfuBOdN5z",
      "meetingUri": "https://meet.google.com/abc-mnop-xyz",
      "meetingCode": "abc-mnop-xyz",
      "config": {
        "accessType": "TRUSTED"
      }
    }
  }
}
```

## LinkedIn

Profil dan postingan lewat akun LinkedIn yang terhubung

### GET /toolkits/linkedin/me

**Ambil profil akun LinkedIn yang terhubung**

- URL: `GET https://api.apipedia.id/v1/toolkits/linkedin/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `getLinkedinProfile`

Dieksekusi lewat tool Composio `LINKEDIN_GET_MY_INFO`. Kembaliannya memuat URN penulis yang dipakai sebagai `author` saat membuat postingan. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum).

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/toolkits/linkedin/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Profil LinkedIn berhasil diambil",
  "data": {
    "tool": "LINKEDIN_GET_MY_INFO",
    "result": {
      "author_id": "urn:li:person:AbCdEf123",
      "name": "Budi Santoso",
      "email": "budi@contoh.com"
    }
  }
}
```

### POST /toolkits/linkedin/posts

**Buat postingan di LinkedIn**

- URL: `POST https://api.apipedia.id/v1/toolkits/linkedin/posts`
- Auth: `Authorization: Bearer <token>`
- operationId: `createLinkedinPost`

Dieksekusi lewat tool Composio `LINKEDIN_CREATE_LINKED_IN_POST`. Field `commentary` (wajib, alias `text`) berisi isi postingan; `author` (wajib) diisi URN dari GET /toolkits/linkedin/me; `visibility` opsional (PUBLIC atau CONNECTIONS). Memerlukan koneksi aktif (409 bila belum). Efek samping nyata: postingan langsung tayang.

Body JSON:

```json
{
  "commentary": "Kami baru merilis integrasi baru.",
  "author": "urn:li:person:AbCdEf123",
  "visibility": "PUBLIC"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/linkedin/posts" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"commentary":"Kami baru merilis integrasi baru.","author":"urn:li:person:AbCdEf123","visibility":"PUBLIC"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Postingan LinkedIn berhasil dibuat",
  "data": {
    "tool": "LINKEDIN_CREATE_LINKED_IN_POST",
    "result": {
      "id": "urn:li:share:7246812345678901234"
    }
  }
}
```

## GitHub

Aksi repositori lewat akun GitHub yang terhubung

### POST /toolkits/github/issues

**Buat issue di repositori GitHub**

- URL: `POST https://api.apipedia.id/v1/toolkits/github/issues`
- Auth: `Authorization: Bearer <token>`
- operationId: `createGithubIssue`

Dieksekusi lewat tool Composio `GITHUB_CREATE_AN_ISSUE`. Field `owner`, `repo`, dan `title` wajib; `body` dan `labels` (array) opsional. Memerlukan koneksi aktif di halaman Koneksi (409 bila belum). Efek samping nyata: issue langsung dibuat.

Body JSON:

```json
{
  "owner": "nama-organisasi",
  "repo": "nama-repo",
  "title": "Bug: unggahan gagal di atas 5 MB",
  "body": "Langkah reproduksi ada di lampiran.",
  "labels": [
    "bug"
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/toolkits/github/issues" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner":"nama-organisasi","repo":"nama-repo","title":"Bug: unggahan gagal di atas 5 MB","body":"Langkah reproduksi ada di lampiran.","labels":["bug"]}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Issue GitHub berhasil dibuat",
  "data": {
    "tool": "GITHUB_CREATE_AN_ISSUE",
    "result": {
      "number": 128,
      "title": "Bug: unggahan gagal di atas 5 MB",
      "state": "open",
      "html_url": "https://github.com/nama-organisasi/nama-repo/issues/128"
    }
  }
}
```

## QRIS

Penguraian payload QRIS EMVCo secara offline (bukan verifikasi merchant)

### POST /qris/parse

**Uraikan payload QRIS dan periksa CRC**

- URL: `POST https://api.apipedia.id/v1/qris/parse`
- Auth: `Authorization: Bearer <token>`
- operationId: `parseQris`

Membaca string hasil pindai QRIS sebagai TLV EMVCo dan menghitung ulang CRC-16/CCITT-FALSE pada tag 63. Seluruhnya offline: ini analisis struktur payload, BUKAN verifikasi merchant ke ASPI atau penyelenggara jasa pembayaran. Payload yang bukan TLV dijawab 400.

Body JSON:

```json
{
  "payload": "00020101021126440014ID.CO.QRIS.WWW0215ID12345678901230303UMI520457325303360540410005802ID5915Warung Apipedia6007Bandung61054011562070703A016304FEDA"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/qris/parse" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payload":"00020101021126440014ID.CO.QRIS.WWW0215ID12345678901230303UMI520457325303360540410005802ID5915Warung Apipedia6007Bandung61054011562070703A016304FEDA"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Payload QRIS berhasil diuraikan",
  "data": {
    "valid_tlv": true,
    "crc_valid": true,
    "crc_tertera": "FEDA",
    "crc_dihitung": "FEDA",
    "payload_format": "01",
    "metode": {
      "kode": "11",
      "keterangan": "statis"
    },
    "merchant": {
      "nama": "Warung Apipedia",
      "kota": "Bandung",
      "kode_pos": "40115",
      "negara": "ID",
      "mcc": "5732",
      "nmid": "ID1234567890123",
      "kriteria": "UMI"
    },
    "transaksi": {
      "mata_uang": "360",
      "nominal": 1000
    },
    "tags": [
      {
        "tag": "00",
        "nama": "Payload Format Indicator",
        "panjang": 2,
        "nilai": "01"
      },
      {
        "tag": "01",
        "nama": "Point of Initiation Method",
        "panjang": 2,
        "nilai": "11"
      },
      {
        "tag": "26",
        "nama": "Merchant Account Information",
        "panjang": 44,
        "nilai": "0014ID.CO.QRIS.WWW0215ID12345678901230303UMI",
        "sub": [
          {
            "tag": "00",
            "nama": null,
            "panjang": 14,
            "nilai": "ID.CO.QRIS.WWW"
          },
          {
            "tag": "02",
            "nama": null,
            "panjang": 15,
            "nilai": "ID1234567890123"
          },
          {
            "tag": "03",
            "nama": null,
            "panjang": 3,
            "nilai": "UMI"
          }
        ]
      }
    ],
    "catatan": "Hasil ini adalah analisis struktur payload secara offline, bukan verifikasi merchant ke ASPI atau penyelenggara jasa pembayaran. crc_valid hanya menandakan payload utuh, bukan bahwa merchant terdaftar atau aktif."
  }
}
```

## Platform

Health check server komputasi (tanpa token)

### GET /health

**Cek kesehatan server komputasi**

- URL: `GET https://api.apipedia.id/v1/health`
- Auth: tidak perlu token
- operationId: `getHealth`

Satu-satunya endpoint Express yang TIDAK butuh Bearer token. Dipakai untuk uptime check. `uptime` dalam detik sejak proses dijalankan.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/health"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "OK",
  "data": {
    "uptime": 86412.37
  }
}
```

## Auth

Login, daftar, refresh, dan logout sesi console (Edge Function auth). Integrasi server sebaiknya memakai API key, bukan login.

### POST /auth/register

**Daftar akun baru**

- URL: `POST https://api.apipedia.id/v1/auth/register`
- Auth: tidak perlu token
- operationId: `authRegister`

Membuat akun console lalu langsung mengembalikan sesi (access + refresh token). TIDAK butuh Bearer, tetapi butuh `turnstile_token` hasil widget Cloudflare Turnstile di halaman daftar, jadi endpoint ini praktis hanya bisa dipanggil dari browser. `name` 2-80 karakter, `password` 8-128 karakter. HTTP 201 saat berhasil.
Error: 400 `Email X sudah terdaftar. Silakan login atau gunakan email lain.`, 400 validasi field (`Field "password" minimal 8 karakter.`), 400 Turnstile gagal, 403 `Akun ini sedang dinonaktifkan.`

Body JSON:

```json
{
  "name": "Budi Santoso",
  "email": "budi@contoh.com",
  "password": "rahasia-kuat-123",
  "turnstile_token": "0.AbCdEf..."
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d '{"name":"Budi Santoso","email":"budi@contoh.com","password":"rahasia-kuat-123","turnstile_token":"0.AbCdEf..."}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Registrasi berhasil",
  "data": {
    "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "v1.MRjcyNTYwODk4NzY1",
    "token_type": "bearer",
    "expires_in": 900,
    "user": {
      "id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "email": "budi@contoh.com",
      "name": "Budi Santoso",
      "role": "user",
      "status": "active",
      "created_at": "2026-09-13T04:00:00Z"
    }
  }
}
```

### POST /auth/login

**Login dengan email dan password**

- URL: `POST https://api.apipedia.id/v1/auth/login`
- Auth: tidak perlu token
- operationId: `authLogin`

Menukar email + password dengan sesi JWT (berlaku 900 detik) dan refresh token. Butuh `turnstile_token`. Untuk integrasi server-ke-server, JANGAN memakai login; buat API key `apk_...` di halaman Token dan pakai itu sebagai Bearer.
Error: 401 `Email atau password salah.`, 400 Turnstile gagal, 403 `Akun ini sedang dinonaktifkan.`

Body JSON:

```json
{
  "email": "budi@contoh.com",
  "password": "rahasia-kuat-123",
  "turnstile_token": "0.AbCdEf..."
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"budi@contoh.com","password":"rahasia-kuat-123","turnstile_token":"0.AbCdEf..."}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Login berhasil",
  "data": {
    "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "v1.MRjcyNTYwODk4NzY1",
    "token_type": "bearer",
    "expires_in": 900,
    "user": {
      "id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "email": "budi@contoh.com",
      "name": "Budi Santoso",
      "role": "user",
      "status": "active",
      "created_at": "2026-09-13T04:00:00Z"
    }
  }
}
```

### POST /auth/refresh

**Perbarui access token**

- URL: `POST https://api.apipedia.id/v1/auth/refresh`
- Auth: tidak perlu token
- operationId: `authRefresh`

Menukar `refresh_token` dengan pasangan token baru. Refresh token lama ikut berganti (rotasi), simpan yang baru. Tidak butuh Bearer.
Error: 401 `Refresh token tidak valid atau sudah kedaluwarsa.`

Body JSON:

```json
{
  "refresh_token": "v1.MRjcyNTYwODk4NzY1"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"v1.MRjcyNTYwODk4NzY1"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Token berhasil diperbarui",
  "data": {
    "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "v1.Nk2LpQ8rS4tU6vW8",
    "token_type": "bearer",
    "expires_in": 900,
    "user": {
      "id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "email": "budi@contoh.com",
      "name": "Budi Santoso",
      "role": "user",
      "status": "active",
      "created_at": "2026-09-13T04:00:00Z"
    }
  }
}
```

### POST /auth/logout

**Logout dan cabut sesi**

- URL: `POST https://api.apipedia.id/v1/auth/logout`
- Auth: tidak perlu token
- operationId: `authLogout`

Dengan `refresh_token` di body, hanya sesi itu yang dicabut. Tanpa body tapi dengan Bearer JWT, SELURUH sesi pengguna dicabut (global). Selalu menjawab sukses supaya logout tidak pernah macet di sisi klien.

Body JSON (opsional):

```json
{
  "refresh_token": "v1.MRjcyNTYwODk4NzY1"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/auth/logout" \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"v1.MRjcyNTYwODk4NzY1"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Logout berhasil",
  "data": {
    "revoked": true
  }
}
```

### GET /auth/me

**Identitas pemegang token**

- URL: `GET https://api.apipedia.id/v1/auth/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `authMe`

Memastikan token yang dipakai valid dan milik siapa. `auth_method` bernilai `jwt` (sesi console) atau `api_key` (kunci `apk_...`). Cara tercepat bagi agent untuk menguji token sebelum memanggil endpoint lain.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/auth/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Profil pengguna berhasil diambil",
  "data": {
    "user": {
      "id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "email": "budi@contoh.com",
      "name": "Budi Santoso",
      "role": "user",
      "status": "active",
      "created_at": "2026-09-13T04:00:00Z"
    },
    "auth_method": "api_key"
  }
}
```

## Akun

Profil, kuota harian, penggunaan, log, paket, dan ringkasan tagihan

### GET /me

**Profil akun**

- URL: `GET https://api.apipedia.id/v1/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `getMe`

Profil lengkap pemanggil (kunci camelCase), termasuk `tier` paket yang menentukan kuota. `tierExpiresAt` adalah kapan tier berbayar berakhir; `null` berarti tidak pernah berakhir (paket Free), bukan data kosong. Tambahkan `?include=quota,connections` untuk ikut mengambil kuota harian dan daftar akun pihak ketiga dalam satu permintaan — keduanya opsional supaya pemanggil yang hanya butuh profil tidak ikut menanggung query tambahan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `include` | query | string | tidak | Bagian tambahan yang ikut disertakan, dipisah koma. Pilihan: `quota`, `connections`. Nilai lain dijawab 400. contoh: quota,connections |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/me?include=quota%2Cconnections" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Profil berhasil diambil",
  "data": {
    "id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "name": "Budi Santoso",
    "email": "budi@contoh.com",
    "role": "user",
    "tier": "free",
    "status": "active",
    "createdAt": "2026-09-13T04:00:00Z",
    "emailVerified": true,
    "lastActiveAt": "2026-10-03T02:15:00Z",
    "company": "PT Contoh Digital",
    "phone": "628123456789",
    "tierExpiresAt": null
  }
}
```

### GET /me/quota

**Kuota harian dan rate limit**

- URL: `GET https://api.apipedia.id/v1/me/quota`
- Auth: `Authorization: Bearer <token>`
- operationId: `getMyQuota`

`used` dihitung dari permintaan nyata hari ini menurut tanggal WIB; `limit` dan `rateLimit` (request per detik) berasal dari paket. `resetAt` adalah tengah malam WIB berikutnya (17:00 UTC). Panggil ini sebelum batch besar untuk memastikan kuota cukup.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/me/quota" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kuota berhasil diambil",
  "data": {
    "used": 137,
    "limit": 500,
    "resetAt": "2026-10-03T17:00:00.000Z",
    "rateLimit": 10,
    "tier": "free"
  }
}
```

### GET /me/onboarding

**Langkah onboarding akun**

- URL: `GET https://api.apipedia.id/v1/me/onboarding`
- Auth: `Authorization: Bearer <token>`
- operationId: `getOnboarding`

Daftar langkah awal di console beserta status selesai/belum.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/me/onboarding" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Langkah onboarding berhasil diambil",
  "data": [
    {
      "id": "verify_email",
      "label": "Verifikasi alamat email",
      "done": true,
      "href": "/settings"
    },
    {
      "id": "create_token",
      "label": "Buat token API pertama",
      "done": true,
      "href": "/api/token"
    },
    {
      "id": "first_request",
      "label": "Kirim permintaan pertama",
      "done": false,
      "href": "https://apipedia.id/sandbox"
    },
    {
      "id": "choose_plan",
      "label": "Aktifkan paket Free",
      "done": true,
      "href": "/billing"
    }
  ]
}
```

### GET /usage

**Ringkasan penggunaan API**

- URL: `GET https://api.apipedia.id/v1/usage`
- Auth: `Authorization: Bearer <token>`
- operationId: `getUsage`

Total, sukses (status 2xx-3xx), gagal, dan rincian per API untuk rentang waktu tertentu. Panggilan ke endpoint platform (auth, token, akun) dikelompokkan sebagai `platform`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `range` | query | string | tidak | Rentang waktu. Nilai lain jatuh ke 7d. pilihan: 7d, 30d, 90d; default: 7d; contoh: 7d |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/usage?range=7d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ringkasan penggunaan berhasil diambil",
  "data": {
    "range": "7d",
    "total": 412,
    "success": 398,
    "failed": 14,
    "byApi": [
      {
        "apiSlug": "ocr",
        "apiName": "ocr",
        "total": 210,
        "success": 205,
        "failed": 5
      },
      {
        "apiSlug": "wilayah",
        "apiName": "Data Wilayah Indonesia",
        "total": 150,
        "success": 150,
        "failed": 0
      },
      {
        "apiSlug": "platform",
        "apiName": "Platform APIPedia",
        "total": 52,
        "success": 43,
        "failed": 9
      }
    ]
  }
}
```

### GET /logs

**Log permintaan (berhalaman)**

- URL: `GET https://api.apipedia.id/v1/logs`
- Auth: `Authorization: Bearer <token>`
- operationId: `getLogs`

Log permintaan milik pemanggil, terbaru dulu. Rentang baca dijepit ke retensi paket: Free 7 hari, Pro dan Business 90 hari.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `page` | query | integer | tidak | Halaman mulai dari 1. default: 1; contoh: 1 |
| `pageSize` | query | integer | tidak | 1-100, default 20. default: 20; contoh: 20 |
| `apiSlug` | query | string | tidak | Saring ke satu slug API. contoh: ocr |
| `status` | query | integer | tidak | Saring ke satu kode status HTTP. contoh: 400 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/logs?page=1&pageSize=20&apiSlug=ocr&status=400" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Log permintaan berhasil diambil",
  "data": {
    "items": [
      {
        "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
        "api_slug": "ocr",
        "api_name": "ocr",
        "method": "POST",
        "path": "/v1/ocr/ktp",
        "status": 200,
        "latency_ms": 1843.212,
        "created_at": "2026-10-03T02:10:44Z"
      }
    ],
    "page": 1,
    "pageSize": 20,
    "total": 412
  }
}
```

### GET /plans

**Daftar paket dan batasnya**

- URL: `GET https://api.apipedia.id/v1/plans`
- Auth: `Authorization: Bearer <token>`
- operationId: `listPlans`

Semua paket membuka seluruh API; yang berbeda hanya kuota harian, rate limit, dan retensi log. Pro dan Business diberikan lewat voucher atau kontrak.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/plans" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar paket berhasil diambil",
  "data": [
    {
      "id": "free",
      "name": "Free",
      "price": 0,
      "currency": "IDR",
      "period": "selamanya",
      "quotaDaily": 500,
      "rateLimit": 10,
      "apiAccess": "penuh",
      "logRetentionDays": 7,
      "description": "Paket bawaan setiap akun. Seluruh API di gateway sudah terbuka.",
      "features": [
        "Akses semua API di gateway",
        "500 request per hari",
        "10 request per detik"
      ]
    },
    {
      "id": "pro",
      "name": "Pro",
      "price": 0,
      "currency": "IDR",
      "period": "selamanya",
      "quotaDaily": 25000,
      "rateLimit": 60,
      "apiAccess": "penuh",
      "logRetentionDays": 90,
      "description": "Kuota lebih besar untuk aplikasi produksi. Diberikan lewat voucher.",
      "features": [
        "Akses semua API di gateway",
        "25.000 request per hari",
        "60 request per detik"
      ]
    }
  ]
}
```

### GET /billing

**Ringkasan paket, kuota, dan transaksi**

- URL: `GET https://api.apipedia.id/v1/billing`
- Auth: `Authorization: Bearer <token>`
- operationId: `getBilling`

Paket aktif, kuota hari ini, dan riwayat transaksi dompet. `nextBillingAt` dan `paymentMethod` selalu null karena tidak ada langganan berbayar.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/billing" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Ringkasan tagihan berhasil diambil",
  "data": {
    "plan": {
      "id": "free",
      "name": "Free",
      "quotaDaily": 500,
      "rateLimit": 10,
      "logRetentionDays": 7
    },
    "quota": {
      "used": 137,
      "limit": 500,
      "resetAt": "2026-10-03T17:00:00.000Z",
      "rateLimit": 10,
      "tier": "free"
    },
    "nextBillingAt": null,
    "paymentMethod": null,
    "transactions": []
  }
}
```

### GET /billing/transactions

**Riwayat transaksi dompet**

- URL: `GET https://api.apipedia.id/v1/billing/transactions`
- Auth: `Authorization: Bearer <token>`
- operationId: `listBillingTransactions`

Sama dengan isi `transactions` pada /billing.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/billing/transactions" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat transaksi berhasil diambil",
  "data": [
    {
      "id": "e7f8a9b0-c1d2-4e3f-8a5b-6c7d8e9f0a1b",
      "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "amount": 50000,
      "type": "topup",
      "category": "topup",
      "description": "Isi saldo via Tripay (qris)",
      "ref_id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "balance_after": 50000,
      "created_at": "2026-09-20T03:00:00Z"
    }
  ]
}
```

## Token

Kelola API key apk_ (buat, ubah cakupan, rotasi, cabut)

### GET /tokens

**Daftar API key aktif**

- URL: `GET https://api.apipedia.id/v1/tokens`
- Auth: `Authorization: Bearer <token>`
- operationId: `listTokens`

API key yang belum dicabut, terbaru dulu. Nilai kunci penuh tidak pernah ditampilkan lagi; hanya `prefix` dan `masked`. `scopes` kosong berarti kunci boleh memanggil seluruh gateway.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tokens" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar API key berhasil diambil",
  "data": {
    "jumlah": 1,
    "tokens": [
      {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "Integrasi Produksi",
        "prefix": "apk_3f9c21ab",
        "masked": "apk_3f9c21ab************",
        "created_at": "2026-09-13T04:00:00Z",
        "last_used_at": "2026-10-03T02:10:44Z",
        "revoked_at": null,
        "expires_at": "2027-09-13T04:00:00Z",
        "scopes": [
          "ocr-ktp",
          "data-wilayah"
        ],
        "rotated_at": null,
        "expired": false
      }
    ]
  }
}
```

### POST /tokens

**Buat API key baru**

- URL: `POST https://api.apipedia.id/v1/tokens`
- Auth: `Authorization: Bearer <token>`
- operationId: `createToken`

Membuat kunci `apk_` + 48 karakter hex. Nilai `key` hanya muncul SEKALI di respons ini, simpan segera. `name` wajib (maks 60). Masa berlaku lewat `expires_in_days` (1-3650) atau `expires_at` (ISO, harus di masa depan); null berarti tanpa batas. `scopes` berisi slug produk katalog (lihat GET /apis); [] atau tidak diisi berarti seluruh gateway. Maksimal 20 kunci aktif per akun. HTTP 201.
Error 400: `Field "name" wajib diisi ...`, `Slug API "X" tidak ada di katalog.`, `Jumlah API key aktif sudah mencapai batas 20. Cabut kunci lama terlebih dahulu.`

Body JSON:

```json
{
  "name": "Integrasi Produksi",
  "expires_in_days": 365,
  "scopes": [
    "ocr-ktp",
    "data-wilayah"
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tokens" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Integrasi Produksi","expires_in_days":365,"scopes":["ocr-ktp","data-wilayah"]}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "API key berhasil dibuat. Simpan sekarang, nilai ini tidak akan ditampilkan lagi.",
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Integrasi Produksi",
    "prefix": "apk_3f9c21ab",
    "masked": "apk_3f9c21ab************",
    "created_at": "2026-09-13T04:00:00Z",
    "last_used_at": null,
    "revoked_at": null,
    "expires_at": "2027-09-13T04:00:00Z",
    "scopes": [
      "ocr-ktp",
      "data-wilayah"
    ],
    "rotated_at": null,
    "expired": false,
    "key": "apk_3f9c21ab7d4e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e"
  }
}
```

### PATCH /tokens/{id}

**Ubah nama, masa berlaku, atau cakupan API key**

- URL: `PATCH https://api.apipedia.id/v1/tokens/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `updateToken`

Sertakan minimal satu dari `name`, `expires_in_days`/`expires_at`, atau `scopes`. Perubahan cakupan dan pencabutan baru berlaku penuh paling lambat 30 detik (cache resolusi kunci).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | UUID API key. contoh: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d |

Body JSON:

```json
{
  "name": "Integrasi Staging",
  "scopes": []
}
```

Contoh request:

```bash
curl -X PATCH "https://api.apipedia.id/v1/tokens/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Integrasi Staging","scopes":[]}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "API key berhasil diperbarui",
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Integrasi Staging",
    "prefix": "apk_3f9c21ab",
    "masked": "apk_3f9c21ab************",
    "created_at": "2026-09-13T04:00:00Z",
    "last_used_at": "2026-10-03T02:10:44Z",
    "revoked_at": null,
    "expires_at": "2027-09-13T04:00:00Z",
    "scopes": [],
    "rotated_at": null,
    "expired": false
  }
}
```

### DELETE /tokens/{id}

**Cabut API key**

- URL: `DELETE https://api.apipedia.id/v1/tokens/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `revokeToken`

Mencabut kunci secara permanen. Id yang tidak ada atau sudah dicabut dijawab 404 `API key dengan id "X" tidak ditemukan atau sudah dicabut.`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/tokens/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "API key berhasil dicabut",
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "revoked": true
  }
}
```

### POST /tokens/{id}/rotate

**Rotasi nilai API key**

- URL: `POST https://api.apipedia.id/v1/tokens/{id}/rotate`
- Auth: `Authorization: Bearer <token>`
- operationId: `rotateToken`

Menerbitkan nilai kunci baru untuk id yang sama (nama, cakupan, masa berlaku tetap). Nilai lama langsung tidak berlaku (dengan jendela cache maksimal 30 detik). `key` baru hanya muncul sekali.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tokens/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/rotate" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "API key berhasil dirotasi. Nilai lama sudah tidak berlaku - simpan nilai baru sekarang.",
  "data": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Integrasi Produksi",
    "prefix": "apk_8e2d4f6a",
    "masked": "apk_8e2d4f6a************",
    "created_at": "2026-09-13T04:00:00Z",
    "last_used_at": "2026-10-03T02:10:44Z",
    "revoked_at": null,
    "expires_at": "2027-09-13T04:00:00Z",
    "scopes": [
      "ocr-ktp",
      "data-wilayah"
    ],
    "rotated_at": "2026-10-03T03:00:00Z",
    "expired": false,
    "key": "apk_8e2d4f6a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e1f2a4b6c"
  }
}
```

## Katalog

Katalog produk API (publik, tanpa token)

### GET /apis

**Katalog produk API**

- URL: `GET https://api.apipedia.id/v1/apis`
- Auth: tidak perlu token
- operationId: `listCatalog`

Seluruh produk di katalog, diurutkan per kategori lalu nama. Tidak butuh token. `slug` di sini adalah nilai yang dipakai untuk `scopes` API key. `status` bernilai `live` atau `soon`.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/apis"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Katalog API berhasil diambil",
  "data": [
    {
      "slug": "ocr-ktp",
      "name": "OCR KTP",
      "category": "Identitas",
      "summary": "Ekstraksi field KTP dari foto",
      "description": "Membaca NIK, nama, alamat, dan field lain dari foto KTP.",
      "tier": "free",
      "methods": [
        "POST"
      ],
      "base_url": "https://<project>.supabase.co/functions/v1/ocr/ktp",
      "open_api_url": null,
      "avg_latency_ms": 1800,
      "status": "live",
      "version": "1.0.0",
      "monthly_calls": 12840,
      "uptime": 99.9,
      "connection": null,
      "endpoints": [
        {
          "method": "POST",
          "path": "/ocr/ktp"
        }
      ]
    }
  ]
}
```

### GET /apis/{slug}

**Detail satu produk API**

- URL: `GET https://api.apipedia.id/v1/apis/{slug}`
- Auth: tidak perlu token
- operationId: `getCatalogItem`

Tidak butuh token. Slug tidak dikenal dijawab 404.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `slug` | path | string | ya | contoh: ocr-ktp |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/apis/ocr-ktp"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail API berhasil diambil",
  "data": {
    "slug": "ocr-ktp",
    "name": "OCR KTP",
    "category": "Identitas",
    "summary": "Ekstraksi field KTP dari foto",
    "tier": "free",
    "methods": [
      "POST"
    ],
    "status": "live",
    "endpoints": [
      {
        "method": "POST",
        "path": "/ocr/ktp"
      }
    ]
  }
}
```

## Dompet

Saldo, mutasi, dan isi saldo lewat Tripay (QRIS, Virtual Account, dan gerai ritel)

### GET /wallet

**Saldo coin**

- URL: `GET https://api.apipedia.id/v1/wallet`
- Auth: `Authorization: Bearer <token>`
- operationId: `getWallet`

Saldo coin dihitung dari buku besar `wallet_transactions`. `rupiahPerCoin` adalah harga 1 coin dalam rupiah: top up dibayar rupiah (`minTopup`/`maxTopup` dalam rupiah) dan coin yang masuk = floor(rupiah / rupiahPerCoin). `topupAvailable` false berarti kredensial Tripay belum diisi di server sehingga top up belum bisa dipakai.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wallet" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Saldo berhasil diambil",
  "data": {
    "balance": 50000,
    "currency": "IDR",
    "minTopup": 10000,
    "maxTopup": 10000000,
    "topupAvailable": true,
    "rupiahPerCoin": 1
  }
}
```

### GET /credits

**Saldo coin dan kredit layanan**

- URL: `GET https://api.apipedia.id/v1/credits`
- Auth: `Authorization: Bearer <token>`
- operationId: `getCredits`

Ringkasan saldo coin, kredit SMS (beserta jenis pesan aktif dan kredit per pesan), dan kredit AI (beserta kuota request gratis harian dan pemakaian hari ini). `coinPerCredit` adalah harga 1 kredit dalam coin.
`ai.grants` berisi dompet kredit khusus dari voucher, mis. Bansos. Dompet ini terpisah dari `ai.balance`: hanya berlaku di provider pemiliknya, tidak bisa dibeli dengan coin, dan sisanya hangus pada `expiresAt`.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/credits" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Saldo kredit berhasil diambil",
  "data": {
    "coins": 125000,
    "sms": {
      "balance": 955,
      "coinPerCredit": 35,
      "types": [
        {
          "id": "otp",
          "label": "SMS OTP",
          "credits": 45
        },
        {
          "id": "sender",
          "label": "SMS Reguler",
          "credits": 30
        }
      ]
    },
    "ai": {
      "balance": 2.5,
      "coinPerCredit": 18500,
      "freeDailyRequests": 10,
      "freeUsedToday": 3,
      "grants": [
        {
          "kind": "ai:bansos",
          "slug": "bansos",
          "label": "Bansos",
          "balance": 10,
          "expiresAt": "2026-11-30T03:00:00Z"
        }
      ]
    }
  }
}
```

### POST /credits/buy

**Tukar coin jadi kredit SMS/AI**

- URL: `POST https://api.apipedia.id/v1/credits/buy`
- Auth: `Authorization: Bearer <token>`
- operationId: `buyCredits`

Membeli kredit layanan dengan coin secara atomik. `kind`: `sms` atau `ai`. `credits`: SMS bilangan bulat 1-1.000.000; AI 0,01-100.000 (2 desimal). Biaya `coinCost = ceil(credits x coinPerCredit)`. Respons memuat saldo kredit (`balance`) dan saldo coin (`coins`) setelah pembelian.

Body JSON:

```json
{
  "kind": "sms",
  "credits": 100
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/credits/buy" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind":"sms","credits":100}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kredit berhasil dibeli",
  "data": {
    "credits": 100,
    "coinCost": 3500,
    "balance": 1055,
    "coins": 121500
  }
}
```

### GET /credits/transactions

**Riwayat kredit**

- URL: `GET https://api.apipedia.id/v1/credits/transactions`
- Auth: `Authorization: Bearer <token>`
- operationId: `listCreditTransactions`

50 mutasi kredit terbaru untuk satu jenis. `type`: purchase, usage, refund, atau adjust. `amount` positif menambah kredit, negatif mengurangi; kredit AI boleh pecahan (6 desimal).

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `kind` | query | string | ya | pilihan: sms, ai; contoh: sms |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/credits/transactions?kind=sms" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat kredit berhasil diambil",
  "data": {
    "items": [
      {
        "id": "0f1e2d3c-4b5a-4968-8776-655443322110",
        "kind": "sms",
        "amount": -45,
        "type": "usage",
        "description": "SMS otp ke 0812****7890",
        "ref_id": "sms_6b1f2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "balance_after": 955,
        "created_at": "2026-10-29T03:15:00Z"
      }
    ]
  }
}
```

### GET /wallet/transactions

**Mutasi saldo**

- URL: `GET https://api.apipedia.id/v1/wallet/transactions`
- Auth: `Authorization: Bearer <token>`
- operationId: `listWalletTransactions`

Buku besar append-only, terbaru dulu. `amount` positif menambah saldo, negatif mengurangi. `type`/`category`: topup, voucher, dan seterusnya.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wallet/transactions" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat saldo berhasil diambil",
  "data": {
    "total": 1,
    "items": [
      {
        "id": "e7f8a9b0-c1d2-4e3f-8a5b-6c7d8e9f0a1b",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "amount": 50000,
        "type": "topup",
        "category": "topup",
        "description": "Isi saldo via Tripay (qris)",
        "ref_id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        "balance_after": 50000,
        "created_at": "2026-09-20T03:00:00Z"
      }
    ]
  }
}
```

### GET /wallet/methods

**Metode pembayaran isi saldo**

- URL: `GET https://api.apipedia.id/v1/wallet/methods`
- Auth: `Authorization: Bearer <token>`
- operationId: `listPaymentMethods`

Kode metode yang diterima `method` pada POST /wallet/topup.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wallet/methods" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Metode pembayaran berhasil diambil",
  "data": {
    "methods": [
      {
        "code": "qris",
        "name": "QRIS",
        "group": "QRIS"
      },
      {
        "code": "bri_va",
        "name": "BRI Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "bni_va",
        "name": "BNI Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "permata_va",
        "name": "Permata Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "cimb_niaga_va",
        "name": "CIMB Niaga Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "maybank_va",
        "name": "Maybank Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "sampoerna_va",
        "name": "Sahabat Sampoerna Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "bnc_va",
        "name": "Bank Neo Commerce Virtual Account",
        "group": "Virtual Account"
      },
      {
        "code": "atm_bersama_va",
        "name": "ATM Bersama",
        "group": "Virtual Account"
      },
      {
        "code": "artha_graha_va",
        "name": "Artha Graha Virtual Account",
        "group": "Virtual Account"
      }
    ]
  }
}
```

### GET /wallet/topups

**Riwayat permintaan isi saldo**

- URL: `GET https://api.apipedia.id/v1/wallet/topups`
- Auth: `Authorization: Bearer <token>`
- operationId: `listTopups`

Status: pending, paid, failed, expired.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wallet/topups" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat isi saldo berhasil diambil",
  "data": {
    "items": [
      {
        "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "amount": 50000,
        "method": "qris",
        "status": "paid",
        "payment_number": "00020101021226...",
        "total_payment": 50350,
        "fee": 350,
        "expired_at": "2026-09-20T04:00:00Z",
        "paid_at": "2026-09-20T03:00:00Z",
        "created_at": "2026-09-20T02:55:00Z",
        "updated_at": "2026-09-20T03:00:00Z"
      }
    ]
  }
}
```

### POST /wallet/topup

**Buat permintaan isi saldo**

- URL: `POST https://api.apipedia.id/v1/wallet/topup`
- Auth: `Authorization: Bearer <token>`
- operationId: `createTopup`

Membuat transaksi di Tripay dan mengembalikan instruksi bayar (`payment_number`: string QRIS atau kode bayar) serta `payUrl` halaman bayar. `amount` bilangan bulat Rp 10.000 - Rp 10.000.000; `method` dari /wallet/methods (default qris). Saldo bertambah otomatis setelah Tripay mengonfirmasi lewat callback, atau saat GET /wallet/topup/{id}. HTTP 201.
Error: 400 nominal/metode tidak valid, 502 Tripay menolak, 503 kredensial Tripay belum diisi.

Body JSON:

```json
{
  "amount": 50000,
  "method": "qris"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/wallet/topup" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount":50000,"method":"qris"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Instruksi pembayaran berhasil dibuat",
  "data": {
    "topup": {
      "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
      "amount": 50000,
      "method": "qris",
      "status": "pending",
      "payment_number": "00020101021226...",
      "total_payment": 50350,
      "fee": 350,
      "expired_at": "2026-09-20T04:00:00Z",
      "paid_at": null,
      "created_at": "2026-09-20T02:55:00Z",
      "updated_at": "2026-09-20T02:55:01Z"
    },
    "payUrl": "https://tripay.co.id/checkout/T0001000000000000001"
  }
}
```

### GET /wallet/topup/{id}

**Status satu isi saldo**

- URL: `GET https://api.apipedia.id/v1/wallet/topup/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getTopup`

Menyinkronkan status ke Tripay sebelum menjawab, jadi aman dipakai untuk polling setelah pembayaran. Ikut mengembalikan saldo terbaru.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: 3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/wallet/topup/3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Status isi saldo berhasil diambil",
  "data": {
    "topup": {
      "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "amount": 50000,
      "method": "qris",
      "status": "paid",
      "paid_at": "2026-09-20T03:00:00Z"
    },
    "balance": 50000
  }
}
```

### POST /wallet/topup/{id}/cancel

**Batalkan isi saldo yang belum dibayar**

- URL: `POST https://api.apipedia.id/v1/wallet/topup/{id}/cancel`
- Auth: `Authorization: Bearer <token>`
- operationId: `cancelTopup`

Status menjadi `failed`. Transaksi yang sudah `paid` dijawab 400 `Transaksi sudah dibayar dan tidak bisa dibatalkan.`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: 3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/wallet/topup/3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f/cancel" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Transaksi isi saldo dibatalkan",
  "data": {
    "topup": {
      "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "amount": 50000,
      "method": "qris",
      "status": "failed"
    }
  }
}
```

## Voucher

Penukaran voucher saldo atau kenaikan paket

### POST /vouchers/redeem

**Tukarkan kode voucher**

- URL: `POST https://api.apipedia.id/v1/vouchers/redeem`
- Auth: `Authorization: Bearer <token>`
- operationId: `redeemVoucher`

Voucher memberi saldo (`rewardType: wallet`), menaikkan paket untuk sejumlah hari (`rewardType: tier`, mis. ke Pro), atau memberi kredit AI pada dompet khusus (`rewardType: credit`, mis. Bansos). Kredit dari voucher terpisah dari kredit AI utama: hanya bisa dipakai di provider pemilik dompet, dan sisanya hangus pada `creditExpiresAt`. Kode tidak peka huruf besar/kecil.
Error 400: `Kode voucher wajib diisi.`, `Kode voucher tidak ditemukan atau sudah tidak aktif.`, `Voucher ini belum aktif.`, `Voucher ini sudah kedaluwarsa.`, `Voucher ini sudah mencapai batas pemakaian.`, `Kamu sudah pernah menukarkan voucher ini.`

Body JSON:

```json
{
  "code": "APIPEDIA-PRO30"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/vouchers/redeem" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code":"APIPEDIA-PRO30"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Voucher berhasil ditukarkan",
  "data": {
    "voucher": {
      "id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
      "rewardType": "credit",
      "amount": null,
      "creditKind": "ai:bansos",
      "creditAmount": 10
    },
    "balance": 50000,
    "tier": "free",
    "tierExpiresAt": null,
    "creditBalance": 10,
    "creditExpiresAt": "2026-11-30T03:00:00Z"
  }
}
```

### GET /vouchers/history

**Riwayat penukaran voucher**

- URL: `GET https://api.apipedia.id/v1/vouchers/history`
- Auth: `Authorization: Bearer <token>`
- operationId: `listVoucherHistory`

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/vouchers/history" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Riwayat penukaran voucher berhasil diambil",
  "data": {
    "items": [
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "voucher_id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "reward_type": "tier",
        "amount": null,
        "created_at": "2026-10-03T03:00:00Z"
      }
    ]
  }
}
```

## Tiket

Tiket dukungan: laporan bug, data salah, tagihan

### GET /tickets/categories

**Kategori tiket dukungan**

- URL: `GET https://api.apipedia.id/v1/tickets/categories`
- Auth: `Authorization: Bearer <token>`
- operationId: `listTicketCategories`

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tickets/categories" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Kategori tiket berhasil diambil",
  "data": {
    "items": [
      {
        "id": "technical",
        "label": "Kendala Teknis"
      },
      {
        "id": "billing",
        "label": "Tagihan & Pembayaran"
      },
      {
        "id": "api-docs",
        "label": "Dokumentasi API"
      },
      {
        "id": "account",
        "label": "Akun"
      },
      {
        "id": "feature",
        "label": "Permintaan Fitur"
      },
      {
        "id": "other",
        "label": "Lainnya"
      }
    ]
  }
}
```

### GET /tickets

**Daftar tiket milik saya**

- URL: `GET https://api.apipedia.id/v1/tickets`
- Auth: `Authorization: Bearer <token>`
- operationId: `listTickets`

Status tiket: open, in_progress, waiting_response, resolved, closed. `ticket_number` (format YYYYMMDDNNNN) adalah nomor yang disebutkan ke tim dukungan.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tickets" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar tiket berhasil diambil",
  "data": {
    "items": [
      {
        "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
        "ticket_number": "202610030001",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "category": "technical",
        "subject": "OCR KTP mengembalikan nama null",
        "description": "Foto KTP jelas tetapi field nama selalu null sejak pagi.",
        "priority": "medium",
        "status": "open",
        "assigned_to": null,
        "created_at": "2026-10-03T02:30:00Z",
        "updated_at": "2026-10-03T02:30:00Z",
        "resolved_at": null,
        "closed_at": null
      }
    ],
    "total": 1
  }
}
```

### POST /tickets

**Buat tiket dukungan**

- URL: `POST https://api.apipedia.id/v1/tickets`
- Auth: `Authorization: Bearer <token>`
- operationId: `createTicket`

Jalur resmi untuk melaporkan bug, data salah, atau masalah tagihan. `category` dari /tickets/categories; `subject` 5-150 karakter; `description` minimal 10 karakter; `priority` low, medium (default), high, atau urgent. HTTP 201.

Body JSON:

```json
{
  "category": "technical",
  "subject": "OCR KTP mengembalikan nama null",
  "description": "Foto KTP jelas tetapi field nama selalu null sejak pagi. Contoh request id ada di log.",
  "priority": "medium"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tickets" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"category":"technical","subject":"OCR KTP mengembalikan nama null","description":"Foto KTP jelas tetapi field nama selalu null sejak pagi. Contoh request id ada di log.","priority":"medium"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tiket berhasil dibuat",
  "data": {
    "ticket": {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "ticket_number": "202610030001",
      "category": "technical",
      "subject": "OCR KTP mengembalikan nama null",
      "priority": "medium",
      "status": "open",
      "created_at": "2026-10-03T02:30:00Z"
    }
  }
}
```

### GET /tickets/{id}

**Detail tiket dan percakapannya**

- URL: `GET https://api.apipedia.id/v1/tickets/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `getTicket`

Pesan internal tim tidak ikut ditampilkan.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | UUID tiket (bukan ticket_number). contoh: b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/tickets/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Detail tiket berhasil diambil",
  "data": {
    "ticket": {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "ticket_number": "202610030001",
      "subject": "OCR KTP mengembalikan nama null",
      "status": "waiting_response"
    },
    "messages": [
      {
        "id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80",
        "ticket_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
        "user_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "message": "Foto KTP jelas tetapi field nama selalu null sejak pagi.",
        "is_internal": false,
        "created_at": "2026-10-03T02:30:00Z"
      }
    ]
  }
}
```

### POST /tickets/{id}/messages

**Balas tiket**

- URL: `POST https://api.apipedia.id/v1/tickets/{id}/messages`
- Auth: `Authorization: Bearer <token>`
- operationId: `replyTicket`

Menambah pesan ke tiket. Tiket berstatus `waiting_response` otomatis kembali `open`. Tiket `closed` tidak bisa dibalas (400). HTTP 201.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e |

Body JSON:

```json
{
  "message": "Sudah saya coba ulang dengan foto baru, hasilnya sama."
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tickets/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/messages" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message":"Sudah saya coba ulang dengan foto baru, hasilnya sama."}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Balasan berhasil dikirim",
  "data": {
    "message": {
      "id": "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8091",
      "ticket_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "message": "Sudah saya coba ulang dengan foto baru, hasilnya sama.",
      "is_internal": false,
      "created_at": "2026-10-03T03:10:00Z"
    },
    "ticket": {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "ticket_number": "202610030001",
      "status": "open"
    }
  }
}
```

### POST /tickets/{id}/close

**Tutup tiket**

- URL: `POST https://api.apipedia.id/v1/tickets/{id}/close`
- Auth: `Authorization: Bearer <token>`
- operationId: `closeTicket`

Tiket yang sudah ditutup dijawab 400 `Tiket sudah ditutup.`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/tickets/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/close" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Tiket berhasil ditutup",
  "data": {
    "ticket": {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "ticket_number": "202610030001",
      "status": "closed",
      "closed_at": "2026-10-03T03:20:00Z"
    }
  }
}
```

## Blog

Baca artikel blog apipedia.id/blog yang sudah terbit (publik, tanpa token)

### GET /blog/posts

**Daftar artikel blog yang terbit**

- URL: `GET https://api.apipedia.id/v1/blog/posts`
- Auth: tidak perlu token
- operationId: `listBlogPosts`

Publik, TANPA Bearer token, di-cache di server. Hanya artikel terbit (dan terjadwal yang waktunya sudah lewat), urut terbaru. `pageSize` maksimal 50. Saring per tag dengan `tag`. Artikel dibaca di https://apipedia.id/blog/{slug}.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `page` | query | integer | tidak | default: 1 |
| `pageSize` | query | integer | tidak | default: 10 |
| `tag` | query | string | tidak | contoh: nik |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/blog/posts?tag=nik"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar artikel berhasil diambil",
  "data": {
    "items": [
      {
        "slug": "cara-validasi-nik-dengan-api",
        "title": "Cara Validasi NIK dengan API",
        "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
        "cover_url": "https://example.supabase.co/storage/v1/object/public/blog-media/posts/2026/10/a1b2.webp",
        "cover_alt": "Ilustrasi kartu KTP dengan NIK disorot",
        "tags": [
          "nik",
          "tutorial"
        ],
        "published_at": "2026-10-01T02:00:00Z",
        "updated_at": "2026-10-02T08:00:00Z",
        "author_name": "Rani Pratama"
      }
    ],
    "page": 1,
    "pageSize": 10,
    "total": 1
  }
}
```

### GET /blog/posts/{slug}

**Baca satu artikel blog**

- URL: `GET https://api.apipedia.id/v1/blog/posts/{slug}`
- Auth: tidak perlu token
- operationId: `getBlogPost`

Publik, TANPA Bearer token. `body_html` sudah disanitasi server. Draf dan artikel terjadwal yang belum waktunya dijawab 404 `Artikel tidak ditemukan atau belum diterbitkan.`

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `slug` | path | string | ya | contoh: cara-validasi-nik-dengan-api |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/blog/posts/cara-validasi-nik-dengan-api"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel berhasil diambil",
  "data": {
    "slug": "cara-validasi-nik-dengan-api",
    "title": "Cara Validasi NIK dengan API",
    "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
    "body_html": "<p>NIK terdiri dari 16 digit ...</p><h2>Struktur NIK</h2><p>...</p>",
    "cover_url": "https://example.supabase.co/storage/v1/object/public/blog-media/posts/2026/10/a1b2.webp",
    "cover_alt": "Ilustrasi kartu KTP dengan NIK disorot",
    "tags": [
      "nik",
      "tutorial"
    ],
    "published_at": "2026-10-01T02:00:00Z",
    "updated_at": "2026-10-02T08:00:00Z",
    "noindex": false,
    "seo_title": "Cara Validasi NIK dengan API (Tanpa Dukcapil)",
    "seo_description": "Validasi NIK 16 digit lewat API: cek kode wilayah, tanggal lahir, dan jenis kelamin dalam satu panggilan.",
    "canonical_url": null,
    "og_image_url": "https://example.supabase.co/storage/v1/object/public/blog-media/posts/2026/10/a1b2.webp",
    "author_name": "Rani Pratama",
    "author_role": "Backend Engineer di APIPedia",
    "author_bio": "Menulis tentang integrasi data identitas Indonesia.",
    "author_avatar": null,
    "author_links": {
      "linkedin": "https://www.linkedin.com/in/contoh"
    }
  }
}
```

## Blog Admin

CMS blog khusus admin (Edge Function blog). Token akun non-admin selalu dijawab 403

### GET /admin/blog

**Admin: daftar semua artikel**

- URL: `GET https://api.apipedia.id/v1/admin/blog`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminListBlogPosts`

KHUSUS ADMIN (Edge Function blog, requireAdmin). Termasuk draf dan terjadwal. Token akun non-admin dijawab 403. Saring `status` (draft, published, scheduled) dan cari `q` di judul/slug.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `status` | query | string | tidak | pilihan: draft, published, scheduled |
| `q` | query | string | tidak | - |
| `page` | query | integer | tidak | default: 1 |
| `pageSize` | query | integer | tidak | default: 20 |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/admin/blog" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Daftar artikel berhasil diambil",
  "data": {
    "items": [
      {
        "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
        "slug": "cara-validasi-nik-dengan-api",
        "title": "Cara Validasi NIK dengan API",
        "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
        "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
        "body_json": {
          "type": "doc",
          "content": []
        },
        "cover_url": null,
        "cover_alt": null,
        "author_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
        "status": "draft",
        "published_at": null,
        "seo_title": null,
        "seo_description": null,
        "canonical_url": null,
        "og_image_url": null,
        "tags": [
          "nik",
          "tutorial"
        ],
        "noindex": false,
        "created_at": "2026-10-03T02:00:00Z",
        "updated_at": "2026-10-03T02:10:00Z"
      }
    ],
    "page": 1,
    "pageSize": 20,
    "total": 1
  }
}
```

### POST /admin/blog

**Admin: buat artikel (draf)**

- URL: `POST https://api.apipedia.id/v1/admin/blog`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminCreateBlogPost`

KHUSUS ADMIN. HTTP 201. `title` wajib; `slug` opsional (dibuat dari judul), harus cocok `^[a-z0-9]+(?:-[a-z0-9]+)*$`, slug yang sudah dipakai dijawab 409. `seo_title` maks 70, `seo_description` maks 170, `tags` maks 10 masing-masing maks 40 karakter. `body_html` disanitasi server; `body_json` adalah dokumen editor untuk dibuka ulang.

Body JSON:

```json
{
  "title": "Cara Validasi NIK dengan API",
  "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil.",
  "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
  "tags": [
    "nik",
    "tutorial"
  ]
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/admin/blog" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Cara Validasi NIK dengan API","excerpt":"NIK 16 digit bisa divalidasi tanpa database Dukcapil.","body_html":"<p>NIK terdiri dari 16 digit ...</p>","tags":["nik","tutorial"]}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel dibuat",
  "data": {
    "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "slug": "cara-validasi-nik-dengan-api",
    "title": "Cara Validasi NIK dengan API",
    "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
    "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
    "body_json": {
      "type": "doc",
      "content": []
    },
    "cover_url": null,
    "cover_alt": null,
    "author_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "status": "draft",
    "published_at": null,
    "seo_title": null,
    "seo_description": null,
    "canonical_url": null,
    "og_image_url": null,
    "tags": [
      "nik",
      "tutorial"
    ],
    "noindex": false,
    "created_at": "2026-10-03T02:00:00Z",
    "updated_at": "2026-10-03T02:10:00Z"
  }
}
```

### GET /admin/blog/{id}

**Admin: detail artikel**

- URL: `GET https://api.apipedia.id/v1/admin/blog/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminGetBlogPost`

KHUSUS ADMIN. Mengembalikan semua kolom termasuk `body_json`.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/admin/blog/c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel berhasil diambil",
  "data": {
    "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "slug": "cara-validasi-nik-dengan-api",
    "title": "Cara Validasi NIK dengan API",
    "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
    "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
    "body_json": {
      "type": "doc",
      "content": []
    },
    "cover_url": null,
    "cover_alt": null,
    "author_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "status": "draft",
    "published_at": null,
    "seo_title": null,
    "seo_description": null,
    "canonical_url": null,
    "og_image_url": null,
    "tags": [
      "nik",
      "tutorial"
    ],
    "noindex": false,
    "created_at": "2026-10-03T02:00:00Z",
    "updated_at": "2026-10-03T02:10:00Z"
  }
}
```

### PATCH /admin/blog/{id}

**Admin: ubah artikel**

- URL: `PATCH https://api.apipedia.id/v1/admin/blog/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminUpdateBlogPost`

KHUSUS ADMIN. Kolom yang tidak dikirim tidak berubah. Aturan validasi sama dengan pembuatan; slug bentrok dijawab 409.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f |

Body JSON:

```json
{
  "title": "Cara Validasi NIK dengan API (2026)",
  "seo_description": "Validasi NIK 16 digit lewat API."
}
```

Contoh request:

```bash
curl -X PATCH "https://api.apipedia.id/v1/admin/blog/c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Cara Validasi NIK dengan API (2026)","seo_description":"Validasi NIK 16 digit lewat API."}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel diperbarui",
  "data": {
    "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "slug": "cara-validasi-nik-dengan-api",
    "title": "Cara Validasi NIK dengan API",
    "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
    "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
    "body_json": {
      "type": "doc",
      "content": []
    },
    "cover_url": null,
    "cover_alt": null,
    "author_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "status": "draft",
    "published_at": null,
    "seo_title": null,
    "seo_description": null,
    "canonical_url": null,
    "og_image_url": null,
    "tags": [
      "nik",
      "tutorial"
    ],
    "noindex": false,
    "created_at": "2026-10-03T02:00:00Z",
    "updated_at": "2026-10-03T02:10:00Z"
  }
}
```

### DELETE /admin/blog/{id}

**Admin: hapus artikel**

- URL: `DELETE https://api.apipedia.id/v1/admin/blog/{id}`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminDeleteBlogPost`

KHUSUS ADMIN. Permanen; alamat publiknya ikut hilang.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/admin/blog/c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel dihapus",
  "data": null
}
```

### POST /admin/blog/media

**Admin: unggah gambar blog**

- URL: `POST https://api.apipedia.id/v1/admin/blog/media`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminUploadBlogImage`

KHUSUS ADMIN. Mengunggah gambar (JPEG, PNG, WebP, GIF, AVIF; maks 5 MB, format dibaca dari isi berkas) ke bucket publik `blog-media` dan mengembalikan URL publiknya untuk `cover_url`, `og_image_url`, atau `<img>` di `body_html`. `name` opsional dipakai sebagai bagian nama berkas di URL.

Body JSON:

```json
{
  "image_base64": "data:image/jpeg;base64,/9j/4AAQ...",
  "name": "cover-api-ongkir"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/admin/blog/media" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"image_base64":"data:image/jpeg;base64,/9j/4AAQ...","name":"cover-api-ongkir"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Gambar berhasil diunggah",
  "data": {
    "url": "https://supabase.apipedia.id/storage/v1/object/public/blog-media/posts/2026/10/cover-api-ongkir-3f9a1c2b.jpg",
    "path": "posts/2026/10/cover-api-ongkir-3f9a1c2b.jpg",
    "mime_type": "image/jpeg",
    "bytes": 57836,
    "width": 1200,
    "height": 630
  }
}
```

### POST /admin/blog/{id}/publish

**Admin: terbitkan, jadwalkan, atau turunkan**

- URL: `POST https://api.apipedia.id/v1/admin/blog/{id}/publish`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminPublishBlogPost`

KHUSUS ADMIN. `publish: true` menerbitkan; `published_at` di masa depan menjadikannya terjadwal (muncul publik otomatis saat waktunya lewat). `publish: false` mengembalikan artikel ke draf.

Parameter:

| Nama | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | contoh: c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f |

Body JSON:

```json
{
  "publish": true,
  "published_at": "2026-10-05T01:00:00Z"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/admin/blog/c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f/publish" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"publish":true,"published_at":"2026-10-05T01:00:00Z"}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Artikel dijadwalkan",
  "data": {
    "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "slug": "cara-validasi-nik-dengan-api",
    "title": "Cara Validasi NIK dengan API",
    "excerpt": "NIK 16 digit bisa divalidasi tanpa database Dukcapil dengan membaca kode wilayah dan tanggal lahirnya.",
    "body_html": "<p>NIK terdiri dari 16 digit ...</p>",
    "body_json": {
      "type": "doc",
      "content": []
    },
    "cover_url": null,
    "cover_alt": null,
    "author_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "status": "scheduled",
    "published_at": "2026-10-05T01:00:00Z",
    "seo_title": null,
    "seo_description": null,
    "canonical_url": null,
    "og_image_url": null,
    "tags": [
      "nik",
      "tutorial"
    ],
    "noindex": false,
    "created_at": "2026-10-03T02:00:00Z",
    "updated_at": "2026-10-03T02:10:00Z"
  }
}
```

### GET /admin/blog/authors/me

**Admin: profil penulis saya**

- URL: `GET https://api.apipedia.id/v1/admin/blog/authors/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminGetMyBlogAuthor`

KHUSUS ADMIN. `data` bernilai null kalau profil penulis belum pernah diisi.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/admin/blog/authors/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN"
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Profil penulis berhasil diambil",
  "data": {
    "profile_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "display_name": "Rani Pratama",
    "role_title": "Backend Engineer di APIPedia",
    "bio": "Menulis tentang integrasi data identitas Indonesia.",
    "avatar_url": null,
    "links": {
      "linkedin": "https://www.linkedin.com/in/contoh"
    }
  }
}
```

### PUT /admin/blog/authors/me

**Admin: simpan profil penulis**

- URL: `PUT https://api.apipedia.id/v1/admin/blog/authors/me`
- Auth: `Authorization: Bearer <token>`
- operationId: `adminSaveMyBlogAuthor`

KHUSUS ADMIN. Data ini tampil publik sebagai byline artikel dan schema.org Person (E-E-A-T). `links` berisi website, linkedin, github, x.

Body JSON:

```json
{
  "display_name": "Rani Pratama",
  "role_title": "Backend Engineer di APIPedia",
  "bio": "Menulis tentang integrasi data identitas Indonesia.",
  "avatar_url": null,
  "links": {
    "linkedin": "https://www.linkedin.com/in/contoh"
  }
}
```

Contoh request:

```bash
curl -X PUT "https://api.apipedia.id/v1/admin/blog/authors/me" \
  -H "Authorization: Bearer $APIPEDIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"display_name":"Rani Pratama","role_title":"Backend Engineer di APIPedia","bio":"Menulis tentang integrasi data identitas Indonesia.","avatar_url":null,"links":{"linkedin":"https://www.linkedin.com/in/contoh"}}'
```

Contoh respons:

```json
{
  "is_success": true,
  "message": "Profil penulis disimpan",
  "data": {
    "profile_id": "9d2c4e6f-1a3b-4c5d-8e7f-0a1b2c3d4e5f",
    "display_name": "Rani Pratama",
    "role_title": "Backend Engineer di APIPedia",
    "bio": "Menulis tentang integrasi data identitas Indonesia.",
    "avatar_url": null,
    "links": {
      "linkedin": "https://www.linkedin.com/in/contoh"
    }
  }
}
```
