Referensi API
Baca struk Anda, beserta teks mentah di baliknya, dari kode Anda sendiri.
Ini adalah API publik. Kunci bearer, paket Pro Plus, akses baca ke data Anda sendiri.
Buat dan cabut key di /settings.
Mengganti atau mengatur ulang kata sandi mencabut semua key.
Pendahuluan
Base URL dan Versi
Semua endpoint berada di bawah /api/v1 dan setiap path diakhiri garis miring. Tanpa garis miring Anda mendapat 308 dan membayar satu perjalanan tambahan — tidak masalah jika klien Anda mengikuti redirect, dan rusak jika tidak. Versi ada di dalam path, sehingga perubahan yang memutus kompatibilitas datang sebagai versi baru, bukan sebagai field yang diam-diam berganti makna.
https://skanota.app/api/v1/
# every path ends in a slash # without it you get a 308 and a second round trip GET https://skanota.app/api/v1/me -> 308 Location: /api/v1/me/ GET https://skanota.app/api/v1/me/ -> 200
Autentikasi
Kirim key Anda sebagai bearer token di setiap request. Key hanya ditampilkan sekali saat dibuat dan tidak bisa dipulihkan. Setiap key membawa scope dan jatah rate limit-nya sendiri, jadi key yang bocor bisa dicabut tanpa mengganggu integrasi Anda yang lain.
curl https://skanota.app/api/v1/me/ \ -H "Authorization: Bearer rs_live_..."
{
"success": true,
"data": {
"userId": "0c8f2b1e-4a77-4c1e-9a2b-6f4d1e9c33aa",
"tier": "pro_plus",
"scopes": ["receipts:read", "receipts:write"],
"scanCredits": {
"limit": 250,
"used": 128,
"rollover": 25,
"purchased": 40,
"remaining": 187,
"resetsAt": "2026-10-01T00:00:00.000Z"
}
}
}Kesalahan
Setiap penolakan memakai amplop yang sama: success bernilai false, error adalah kalimat untuk manusia, dan code adalah pengenal stabil untuk program. Bercabanglah pada code — kalimatnya bisa diperbaiki sewaktu-waktu. Kegagalan autentikasi sengaja dibuat tidak dapat dibedakan satu sama lain, sehingga key yang hilang, tidak dikenal, atau sudah dicabut terbaca sama.
curl -i https://skanota.app/api/v1/receipts/ \ -H "Authorization: Bearer rs_live_wrong"
HTTP/2 401
www-authenticate: Bearer realm="Skanota API"
{
"success": false,
"error": "Invalid or missing API key",
"code": "UNAUTHORIZED"
}
# codes you can branch on
# UNAUTHORIZED 401 no key, malformed, unknown, revoked or expired
# EMAIL_UNVERIFIED 403 valid key, account has not confirmed its email
# FORBIDDEN 403 valid key, account below the Pro Plus plan
# INSUFFICIENT_SCOPE 403 key does not carry the scope this route needs
# NOT_FOUND 404 no such receipt for this account
# INVALID_PARAMETER 400 a query parameter was not one of the accepted values
# RATE_LIMITED 429 budget spent; see Retry-After
# INTERNAL_ERROR 500Batas Laju
600 request per jam, dihitung per key dan bukan per akun — satu integrasi yang lepas kendali tidak bisa mencekik yang lain. Respons yang berhasil membawa sisa jatah, dan 429 membawa Retry-After dalam detik.
curl -i https://skanota.app/api/v1/receipts/ \ -H "Authorization: Bearer rs_live_..."
HTTP/2 200
x-ratelimit-limit: 600
x-ratelimit-remaining: 597
# and when the budget is spent
HTTP/2 429
retry-after: 2841
{ "success": false, "error": "Rate limit exceeded", "code": "RATE_LIMITED" }Struk
Objek Receipt
Satu struk hasil pindai. Setiap endpoint yang mengembalikan struk memakai bentuk ini, begitu pula ekspor arsip, jadi hanya ada satu format untuk ditulis parser-nya. Versi skema dibawa pada setiap objek, bukan hanya pada amplopnya — satu baris ekspor harus tetap bisa menyebut dirinya setelah dipisahkan dari yang lain.
Atribut
schemastring- Versi format objek ini. Saat ini skanota.receipt.v1.
idstring- Pengenal struk, dan nilai yang Anda kirim ke endpoint retrieve.
merchant.namestringboleh null- Nama toko atau restoran, sebagaimana tercetak.
merchant.addressstringboleh null- Alamat yang tercetak di struk, bila ada.
merchant.phonestringboleh null- Nomor telepon yang tercetak di struk, bila ada.
receiptNumberstringboleh null- Nomor transaksi atau nomor struk yang dicetak merchant.
transactionDatestringboleh null- Tanggal dan waktu yang tercetak di kertas, tanpa zona waktu. Ini label jam dinding, bukan satu titik waktu: model bisa membaca jam di struk tetapi tidak bisa tahu zona waktu tokonya, jadi jangan menempelkan zona waktu apa pun.
amounts.totalstringboleh null- Total akhir. Berupa string desimal, bukan angka — jangan lewatkan melalui tipe float.
amounts.taxstringboleh null- Pajak, bila struk memisahkannya.
amounts.subtotalstringboleh null- Total sebelum pajak dan biaya layanan, bila tercetak.
amounts.discountstringboleh null- Diskon yang diterapkan, bila tercetak.
amounts.serviceChargestringboleh null- Biaya layanan, bila tercetak.
amounts.currencystring- Selalu IDR. Disebutkan agar konsumen tidak perlu menyimpulkannya dari deretan angka.
category.builtInstringboleh null- Salah satu kunci kategori bawaan, bila struk punya.
category.customIdstringboleh null- Pengenal kategori yang dibuat akun, bila struk justru masuk ke sana.
notesstringboleh null- Catatan apa pun yang diketik pemilik akun pada struk ini.
tagsarray- Tag pada struk. Selalu berupa array — daftar yang tidak ada dan daftar kosong berarti sama, jadi Anda tidak perlu bercabang untuk membedakannya.
isVerifiedboolean- Apakah seseorang sudah mencocokkan field hasil ekstraksi dengan kertasnya.
ocr.confidencenumberboleh null- Seberapa yakin model secara keseluruhan, antara 0 dan 1.
ocr.rawTextstringboleh null- Semua yang dibaca model dari struk, tanpa diurai. Field inilah yang membuat arsip layak disimpan.
ocr.fieldCountinteger- Berapa banyak field yang berhasil ditemukan model pada gambar.
imagePathstringboleh null- Ke mana harus mengambil gambar, atau null bila struk tidak punya. Berupa path di API ini, tidak pernah alamat penyimpanan aslinya.
createdAtstringboleh null- Kapan struk dipindai. Satu titik waktu sungguhan, sebagai string ISO.
updatedAtstringboleh null- Kapan struk terakhir diubah. Satu titik waktu sungguhan, sebagai string ISO.
{
"schema": "skanota.receipt.v1",
"id": "8f14e45f-ceea-467a-9a36-dedd4bea2543",
"merchant": {
"name": "GRAND LUCKY SUPERSTORE",
"address": "Jl. Jend. Sudirman Kav. 52-53, Jakarta",
"phone": "+62215155678"
},
"receiptNumber": "TRX-20260812-0043",
"transactionDate": "2026-08-12T19:24:00",
"amounts": {
"total": "184500.00",
"tax": "18450.00",
"subtotal": "166050.00",
"discount": null,
"serviceCharge": null,
"currency": "IDR"
},
"category": { "builtIn": "grocery", "customId": null },
"notes": null,
"tags": [],
"isVerified": true,
"ocr": {
"confidence": 0.94,
"rawText": "GRAND LUCKY SUPERSTORE\n...",
"fieldCount": 11
},
"imagePath": "/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/image/",
"createdAt": "2026-08-12T12:31:07.412Z",
"updatedAt": "2026-08-12T12:31:07.412Z"
}Daftar Struk
GET/api/v1/receipts
Mengembalikan satu halaman struk, terbaru lebih dulu kecuali Anda meminta sebaliknya. Endpoint ini menerima filter yang sama dengan yang dipakai aplikasi Skanota, jadi filter yang disimpan di produk dan kueri yang ditulis di sini berarti hal yang sama.
Parameter
pageintegeropsionalHalaman keberapa yang dikembalikan, dimulai dari 1.
Nilai bawaan: 1
limitintegeropsionalBerapa struk per halaman. Nilai di atas batas dipangkas ke batas itu, bukan ditolak.
Nilai bawaan: 20
sortenumopsionalUrutan pengembalian struk.
Salah satu dari: newest · oldest · highest · lowest
Nilai bawaan: newest
statusenumopsionalKembalikan hanya struk terverifikasi, atau hanya yang belum. Kosongkan untuk keduanya.
Salah satu dari: verified · pending
categorystringopsionalKunci kategori bawaan, atau custom:<id> untuk kategori yang dibuat akun. Awalan itulah yang membedakan keduanya. Nilai yang tidak cocok dengan keduanya diabaikan, bukan ditolak.
monthstringopsionalSatu bulan kalender, ditulis YYYY-MM.
searchstringopsionalDicocokkan dengan nama merchant dan nomor struk. Lebih dari 255 karakter dipotong, karena kata kunci yang lebih panjang tidak mungkin cocok dengan kolom mana pun.
Mengembalikan
Daftar objek receipt, beserta blok pagination berisi halaman, ukuran halaman, jumlah total yang cocok, dan apakah masih ada halaman berikutnya.
curl "https://skanota.app/api/v1/receipts/?limit=2&sort=highest" \ -H "Authorization: Bearer rs_live_..."
{
"success": true,
"data": [
{
"schema": "skanota.receipt.v1",
"id": "8f14e45f-ceea-467a-9a36-dedd4bea2543",
"merchant": {
"name": "GRAND LUCKY SUPERSTORE",
"address": "Jl. Jend. Sudirman Kav. 52-53, Jakarta",
"phone": "+62215155678"
},
"receiptNumber": "TRX-20260812-0043",
"transactionDate": "2026-08-12T19:24:00",
"amounts": {
"total": "184500.00",
"tax": "18450.00",
"subtotal": "166050.00",
"discount": null,
"serviceCharge": null,
"currency": "IDR"
},
"category": { "builtIn": "grocery", "customId": null },
"notes": null,
"tags": [],
"isVerified": true,
"ocr": {
"confidence": 0.94,
"rawText": "GRAND LUCKY SUPERSTORE\n...",
"fieldCount": 11
},
"imagePath": "/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/image/",
"createdAt": "2026-08-12T12:31:07.412Z",
"updatedAt": "2026-08-12T12:31:07.412Z"
}
],
"pagination": { "page": 1, "limit": 2, "total": 418, "hasMore": true }
}Ambil Satu Struk
GET/api/v1/receipts/{id}
Mengembalikan satu struk berdasarkan id, termasuk teks mentah yang dibaca model. Struk milik akun lain dijawab 404, bukan 403 — 403 justru akan memastikan bahwa pengenal itu ada.
Parameter
Endpoint ini tidak menerima query parameter.
Mengembalikan
Satu objek receipt.
curl https://skanota.app/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/ \ -H "Authorization: Bearer rs_live_..."
{ "success": true, "data": {
"schema": "skanota.receipt.v1",
"id": "8f14e45f-ceea-467a-9a36-dedd4bea2543",
"merchant": {
"name": "GRAND LUCKY SUPERSTORE",
"address": "Jl. Jend. Sudirman Kav. 52-53, Jakarta",
"phone": "+62215155678"
},
"receiptNumber": "TRX-20260812-0043",
"transactionDate": "2026-08-12T19:24:00",
"amounts": {
"total": "184500.00",
"tax": "18450.00",
"subtotal": "166050.00",
"discount": null,
"serviceCharge": null,
"currency": "IDR"
},
"category": { "builtIn": "grocery", "customId": null },
"notes": null,
"tags": [],
"isVerified": true,
"ocr": {
"confidence": 0.94,
"rawText": "GRAND LUCKY SUPERSTORE\n...",
"fieldCount": 11
},
"imagePath": "/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/image/",
"createdAt": "2026-08-12T12:31:07.412Z",
"updatedAt": "2026-08-12T12:31:07.412Z"
} }Ambil Gambar Struk
GET/api/v1/receipts/{id}/image
Mengalirkan byte gambar. Inilah alamat yang ditunjuk field imagePath, dan gambar disajikan lewat API ini alih-alih langsung dari penyimpanan, sehingga alamat berkas aslinya tidak pernah dipublikasikan. Mintalah salinan yang diperkecil bila Anda menggambar daftar — gambar tersimpan bisa mencapai 1500 piksel pada sisi terpanjangnya.
Parameter
wenumopsionalKembalikan salinan yang diperkecil alih-alih gambar tersimpan. Sisi terpendek akan berukuran minimal sekian piksel, ke arah mana pun struk itu menghadap. Hanya nilai yang tercantum yang diterima — selain itu ditolak, bukan dibulatkan ke yang terdekat.
Salah satu dari: 160 · 320
Mengembalikan
Gambarnya sendiri, bukan JSON. Di-cache secara privat selama satu jam, karena byte untuk satu pengenal tidak pernah berubah.
curl https://skanota.app/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/image/?w=320 \ -H "Authorization: Bearer rs_live_..." \ -o receipt.jpg
HTTP/2 200 content-type: image/jpeg cache-control: private, max-age=3600 referrer-policy: no-referrer <binary>
Arsip
Daftar Arsip
GET/api/v1/archive
Seluruh struk pada akun, satu halaman sekaligus dan tanpa filter. Ini endpoint untuk menyalin isi akun; gunakan Daftar struk bila Anda ingin mencari sesuatu.
Parameter
pageintegeropsionalHalaman keberapa yang dikembalikan, dimulai dari 1.
Nilai bawaan: 1
limitintegeropsionalBerapa struk per halaman. Lebih tinggi daripada endpoint daftar, karena endpoint ini untuk menyalin isi akun, bukan untuk mengisi satu layar.
Nilai bawaan: 50
Mengembalikan
Daftar objek receipt, beserta blok pagination.
curl "https://skanota.app/api/v1/archive/?page=1&limit=100" \ -H "Authorization: Bearer rs_live_..."
{
"success": true,
"data": [ /* receipt objects */ ],
"pagination": { "page": 1, "limit": 100, "total": 418, "hasMore": true }
}Ekspor Arsip
GET/api/v1/archive/export
Seluruh arsip dalam satu respons, satu objek JSON per baris. Dipisah per baris alih-alih satu array besar agar konsumen bisa membacanya satu struk sekaligus, bukan menahan seluruh akun di memori sebelum byte pertama menjadi valid.
Parameter
formatenumopsionalFormat respons.
Salah satu dari: jsonl · json
Nilai bawaan: jsonl
Mengembalikan
Body JSON yang dipisah per baris, atau satu objek JSON berisi jumlah dan sebuah array bila format bernilai json.
curl https://skanota.app/api/v1/archive/export/ \ -H "Authorization: Bearer rs_live_..." > archive.jsonl
content-type: application/x-ndjson; charset=utf-8
{"schema":"skanota.receipt.v1","id":"8f14e45f-...","merchant":{...}}
{"schema":"skanota.receipt.v1","id":"1c383cd3-...","merchant":{...}}
{"schema":"skanota.receipt.v1","id":"9f9d51bc-...","merchant":{...}}Analitik
Ambil Analitik
GET/api/v1/analytics
Pengeluaran yang diagregasi per bulan, per kategori, dan per merchant dalam satu periode. Sengaja lebih sempit daripada angka yang ditampilkan halaman Analitik: beberapa panel di sana dibentuk oleh apa yang enak dilihat pada grafik, bukan oleh apa yang benar tentang datanya, dan keputusan tampilan tidak punya tempat di dalam kontrak API.
Parameter
periodenumopsionalSeberapa jauh ke belakang agregasi dihitung, diselesaikan memakai jam kami, yaitu UTC. Nilai since dan until yang valid menggantikannya dan menentukan sendiri rentangnya; period baru dikembalikan bila menamai rentang yang sama.
Salah satu dari: 1month · 3months · 6months · 1year · all
Nilai bawaan: 6months
categorystringopsionalKunci kategori bawaan, atau custom:<id> untuk kategori yang dibuat akun. Awalan itulah yang membedakan keduanya. Nilai yang tidak cocok dengan keduanya diabaikan, bukan ditolak.
sincestringopsionalAwal rentang, inklusif, ditulis YYYY-MM-DDTHH:mm:ss tanpa zona waktu — jam dinding, seperti tanggal yang tercetak pada struk. Kirim bersama until, atau jangan sama sekali: pasangan yang tidak lengkap akan kembali memakai period.
Nilai bawaan: derived from period
untilstringopsionalAkhir rentang, eksklusif, dengan format yang sama seperti since. Struk yang bertanda waktu tepat pada titik ini termasuk rentang berikutnya, bukan rentang ini.
Nilai bawaan: derived from period
Mengembalikan
Rentang yang dicakupnya berupa awal inklusif dan akhir eksklusif, tiga agregat — monthly, categories, dan merchants — serta period, yang hanya dikembalikan bila menamai rentang itu dan bernilai null jika tidak.
curl "https://skanota.app/api/v1/analytics/?since=2026-06-01T00:00:00&until=2026-08-01T00:00:00" \ -H "Authorization: Bearer rs_live_..."
{
"success": true,
"data": {
"period": null,
"since": "2026-06-01T00:00:00.000Z",
"until": "2026-08-01T00:00:00.000Z",
"monthly": [
{ "month": "2026-06", "total": "4812000.00", "count": 61 },
{ "month": "2026-07", "total": "5140500.00", "count": 74 }
],
"categories": [
{ "category": "grocery", "total": "3910000.00", "count": 52 }
],
"merchants": [
{ "merchant": "GRAND LUCKY SUPERSTORE", "total": "1284000.00", "count": 12 }
]
}
}Akun
Ambil Data Akun
GET/api/v1/me
Milik siapa key ini, akun berada di paket apa, apa saja yang boleh dilakukan key tersebut, dan berapa sisa kuota pindai. Panggil ini lebih dulu: 200 berarti kredensialnya bekerja, dan 401 atau 403 memberi tahu Anda masalah mana yang sedang dihadapi tanpa perlu menghabiskan satu request sungguhan untuk mencari tahu.
Parameter
Endpoint ini tidak menerima query parameter.
Mengembalikan
Pengenal akun, tier-nya, scope pada key yang dikirim, dan sisa kredit pindai beserta tanggal reset-nya.
curl https://skanota.app/api/v1/me/ \ -H "Authorization: Bearer rs_live_..."
{
"success": true,
"data": {
"userId": "0c8f2b1e-4a77-4c1e-9a2b-6f4d1e9c33aa",
"tier": "pro_plus",
"scopes": ["receipts:read", "receipts:write"],
"scanCredits": {
"limit": 250,
"used": 128,
"rollover": 25,
"purchased": 40,
"remaining": 187,
"resetsAt": "2026-10-01T00:00:00.000Z"
}
}
}