Langsung ke konten utama

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.

Request
https://skanota.app/api/v1/
Response
# 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.

Request
curl https://skanota.app/api/v1/me/ \
  -H "Authorization: Bearer rs_live_..."
Response
{
  "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.

Request
curl -i https://skanota.app/api/v1/receipts/ \
  -H "Authorization: Bearer rs_live_wrong"
Response
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     500

Batas 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.

Request
curl -i https://skanota.app/api/v1/receipts/ \
  -H "Authorization: Bearer rs_live_..."
Response
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.
Response
{
  "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

pageintegeropsional

Halaman keberapa yang dikembalikan, dimulai dari 1.

Nilai bawaan: 1

limitintegeropsional

Berapa struk per halaman. Nilai di atas batas dipangkas ke batas itu, bukan ditolak.

Nilai bawaan: 20

sortenumopsional

Urutan pengembalian struk.

Salah satu dari: newest · oldest · highest · lowest

Nilai bawaan: newest

statusenumopsional

Kembalikan hanya struk terverifikasi, atau hanya yang belum. Kosongkan untuk keduanya.

Salah satu dari: verified · pending

categorystringopsional

Kunci kategori bawaan, atau custom:<id> untuk kategori yang dibuat akun. Awalan itulah yang membedakan keduanya. Nilai yang tidak cocok dengan keduanya diabaikan, bukan ditolak.

monthstringopsional

Satu bulan kalender, ditulis YYYY-MM.

searchstringopsional

Dicocokkan 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.

Request
curl "https://skanota.app/api/v1/receipts/?limit=2&sort=highest" \
  -H "Authorization: Bearer rs_live_..."
Response
{
  "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.

Request
curl https://skanota.app/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/ \
  -H "Authorization: Bearer rs_live_..."
Response
{ "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

wenumopsional

Kembalikan 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.

Request
curl https://skanota.app/api/v1/receipts/8f14e45f-ceea-467a-9a36-dedd4bea2543/image/?w=320 \
  -H "Authorization: Bearer rs_live_..." \
  -o receipt.jpg
Response
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

pageintegeropsional

Halaman keberapa yang dikembalikan, dimulai dari 1.

Nilai bawaan: 1

limitintegeropsional

Berapa 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.

Request
curl "https://skanota.app/api/v1/archive/?page=1&limit=100" \
  -H "Authorization: Bearer rs_live_..."
Response
{
  "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

formatenumopsional

Format 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.

Request
curl https://skanota.app/api/v1/archive/export/ \
  -H "Authorization: Bearer rs_live_..." > archive.jsonl
Response
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

periodenumopsional

Seberapa 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

categorystringopsional

Kunci kategori bawaan, atau custom:<id> untuk kategori yang dibuat akun. Awalan itulah yang membedakan keduanya. Nilai yang tidak cocok dengan keduanya diabaikan, bukan ditolak.

sincestringopsional

Awal 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

untilstringopsional

Akhir 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.

Request
curl "https://skanota.app/api/v1/analytics/?since=2026-06-01T00:00:00&until=2026-08-01T00:00:00" \
  -H "Authorization: Bearer rs_live_..."
Response
{
  "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.

Request
curl https://skanota.app/api/v1/me/ \
  -H "Authorization: Bearer rs_live_..."
Response
{
  "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"
    }
  }
}