REST API · Mobile POS

API Reference — SAPI Mobile POS

Referensi lengkap endpoint REST API untuk aplikasi kasir mobile SAPI. Semua endpoint mengembalikan JSON dan menggunakan autentikasi Bearer token via Laravel Sanctum.

BASE URL https://sapi-v1.obiwannn.web.id/api/v1/mobile

Autentikasi

API menggunakan Laravel Sanctum Bearer Token. Login melalui POST /api/v1/mobile/login untuk mendapatkan token, lalu sertakan di setiap request terproteksi.

Header wajib untuk endpoint terproteksi

Authorization: Bearer {token}
Accept: application/json
Content-Type: application/json

Multi-tenant & Role

Setiap pengguna terikat pada satu tenant. Semua data difilter otomatis berdasarkan tenant user yang login — tidak perlu mengirim tenant_id secara eksplisit.

Role yang tersedia: owner dan cashier. Beberapa endpoint hanya bisa diakses oleh role tertentu (ditandai dengan badge pada tiap endpoint).

One device, one token

Setiap login baru akan menghapus token lama untuk akun yang sama. Simpan token dengan aman di sisi client.

Konvensi & Kode Status

Semua request dan response menggunakan format JSON. Tambahkan header Accept: application/json agar error juga dikembalikan sebagai JSON.

200
OK — request berhasil
201
Created — resource berhasil dibuat
401
Unauthorized — token tidak valid atau tidak dikirim
403
Forbidden — role tidak cukup atau bukan milik tenant ini
422
Unprocessable — validasi gagal atau error bisnis
429
Too Many Requests — rate limit terlampaui
Bentuk response error (422)
{
  "message": "Email atau password salah."
}
Rate Limit

POST /api/v1/mobile/login — maksimum 5 request per menit per IP.
Endpoint lain tidak memiliki rate limit khusus.

Auth

POST
/login
Tanpa Header 5/menit

Login kasir atau owner. Menghapus token lama dan mengembalikan token Sanctum baru beserta informasi user dan tenant.

Request Body
FieldTipeKeterangan
email WAJIBstringAlamat email terdaftar
password WAJIBstringPassword pengguna
Request Body (JSON)
{
  "email": "kasir@cafesapi.com",
  "password": "rahasia123"
}
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/login \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "kasir@cafesapi.com",
    "password": "rahasia123"
  }'
Response 200 OK
{
  "token": "1|abc123xyz...",
  "user": {
    "id": 5,
    "name": "Budi Santoso",
    "email": "kasir@cafesapi.com",
    "role": "cashier"
  },
  "tenant": {
    "id": 1,
    "name": "SAPI Cafe",
    "address": "Jl. Sudirman No. 12",
    "phone": "+62812345678"
  }
}
POST
/logout
Bearer Token

Mencabut token saat ini. Gunakan saat pengguna keluar dari aplikasi.

Header Wajib
Authorization: Bearer {token}
Accept: application/json
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/logout \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
Response 200 OK
{
  "message": "Logout berhasil."
}

Tenant & Produk

GET
/tenant/profile
Bearer Token

Mengembalikan informasi tenant milik pengguna yang sedang login (nama, alamat, telepon).

Header Wajib
Authorization: Bearer {token}
Accept: application/json
Contoh Request (cURL)
curl https://sapi-v1.obiwannn.web.id/api/v1/mobile/tenant/profile \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
Response 200 OK
{
  "data": {
    "id": 1,
    "name": "SAPI Cafe",
    "address": "Jl. Sudirman No. 12",
    "phone": "+62812345678"
  }
}
GET
/products
Bearer Token

Mengembalikan semua produk aktif beserta varian yang memiliki stok > 0 dan kategorinya. Digunakan untuk membangun daftar menu di kasir.

Header Wajib
Authorization: Bearer {token}
Accept: application/json
Response 200 OK
{
  "data": [
    {
      "id": 1,
      "name": "Kopi Susu",
      "category_id": 2,
      "category": { "id": 2, "name": "Minuman" },
      "variants": [
        {
          "id": 10,
          "product_id": 1,
          "name": "Regular",
          "price": 18000,
          "stock": 50
        }
      ]
    }
  ]
}

Cash Drawer

GET
/cash-drawer/status
Bearer Token

Mengecek apakah pengguna saat ini memiliki sesi kas yang sedang terbuka.

Header Wajib
Authorization: Bearer {token}
Accept: application/json
Response 200 OK
// Sesi kas terbuka
{
  "is_open": true,
  "drawer_id": 7,
  "opened_at": "2026-05-29T08:00:00.000000Z"
}

// Tidak ada sesi terbuka
{
  "is_open": false,
  "drawer_id": null,
  "opened_at": null
}
POST
/cash-drawer/open
Cashier / Owner

Membuka sesi kas baru dengan modal awal. Gagal jika sudah ada sesi kas yang terbuka untuk user ini.

Request Body
FieldTipeKeterangan
opening_amount WAJIBnumericModal awal kas (min 0)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Content-Type: application/json
Request Body (JSON)
{
  "opening_amount": 500000
}
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/cash-drawer/open \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "opening_amount": 500000 }'
Response 201 Created
{
  "message": "Kas berhasil dibuka.",
  "drawer_id": 7,
  "opened_at": "2026-05-29T08:00:00.000000Z"
}
POST
/cash-drawer/close
Cashier / Owner

Menutup sesi kas aktif. Sistem otomatis menghitung selisih antara kas yang diharapkan (berdasarkan transaksi) dan kas aktual yang dihitung pengguna.

Request Body
FieldTipeKeterangan
closing_amount WAJIBnumericJumlah kas fisik yang dihitung saat tutup (min 0)
notes OPSIONALstringCatatan penutupan kas (maks 500 karakter)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Content-Type: application/json
Request Body (JSON)
{
  "closing_amount": 520000,
  "notes": "Sesuai"
}
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/cash-drawer/close \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "closing_amount": 520000, "notes": "Sesuai" }'
Response 200 OK
{
  "message": "Kas berhasil ditutup.",
  "drawer_id": 7,
  "expected_amount": 515000,
  "closing_amount": 520000,
  "difference": 5000
}
GET
/cash-drawer/{cashDrawer}/summary
Cashier / Owner

Mengembalikan ringkasan sesi kas: detail buka/tutup, jumlah transaksi, dan rincian per metode pembayaran. Kasir hanya bisa melihat sesi kasnya sendiri; owner bisa melihat sesi siapapun dalam tenant yang sama.

Path Parameter
ParameterTipeKeterangan
cashDrawer WAJIBintegerID sesi kas (drawer_id dari endpoint buka/tutup)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Response 200 OK
{
  "data": {
    "drawer": {
      "id": 7,
      "opening_amount": 500000,
      "closing_amount": 520000,
      "expected_amount": 515000,
      "difference": 5000,
      "notes": "Sesuai",
      "opened_at": "2026-05-29T08:00:00.000000Z",
      "closed_at": "2026-05-29T17:00:00.000000Z",
      "is_open": false
    },
    "transaction_count": 45,
    "payment_summary": [
      { "name": "Tunai", "type": "cash", "total": 300000 },
      { "name": "QRIS", "type": "digital", "total": 215000 }
    ]
  }
}

Transaksi

POST
/transactions
Bearer Token

Membuat transaksi POS baru. Stok langsung dikurangi saat endpoint ini dipanggil.
Dua mode: transaksi langsung (kirim payments, status completed) atau open bill (is_open_bill: true, pembayaran menyusul via endpoint Bayar Open Bill).

Request Body
FieldTipeKeterangan
items WAJIBarrayMin 1 item
items[].variant_id WAJIBintegerID varian produk
items[].variant_name WAJIBstringNama varian (disimpan di snapshot transaksi)
items[].qty WAJIBintegerJumlah (min 1)
items[].notes OPSIONALstringCatatan item (maks 500 karakter)
items[].modifiers OPSIONALarrayModifier/topping item
items[].modifiers[].id WAJIB*integerID modifier
is_open_bill OPSIONALbooleanJika true, field payments tidak diperlukan. Ditolak 422 bila outlet mematikan setelan "izinkan tagihan terbuka"; tagihan yang sudah terbuka tetap bisa dilunasi
order_type OPSIONALstringdine_in atau takeaway
customer_name OPSIONALstringNama pelanggan (maks 255)
table_number OPSIONALstringNomor meja (maks 50)
notes OPSIONALstringCatatan transaksi (maks 500)
payments WAJIB†array†Wajib jika bukan open bill
payments[].payment_method_id WAJIB†integerID metode pembayaran
payments[].amount WAJIB†numericNominal pembayaran (min 0)
payments[].reference_code OPSIONALstringKode referensi transfer/QRIS (maks 255)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Content-Type: application/json
Contoh Request — Transaksi Langsung
{
  "items": [
    {
      "variant_id": 10,
      "variant_name": "Kopi Susu Regular",
      "qty": 2,
      "notes": "Gula sedikit",
      "modifiers": [{ "id": 3 }]
    }
  ],
  "order_type": "dine_in",
  "table_number": "A2",
  "payments": [
    { "payment_method_id": 1, "amount": 40000 }
  ]
}
Response 201 Created
{
  "message": "Transaksi berhasil.",
  "transaction_id": 42,
  "code": "TRX-2026-001",
  "total_amount": 36000,
  "change_amount": 4000,
  "status": "completed",
  "is_open_bill": false
}
GET
/transactions
Cashier / Owner

Mengembalikan daftar transaksi dengan pagination. Kasir hanya melihat transaksinya sendiri; owner melihat semua transaksi tenant.

Query Parameters
ParameterTipeKeterangan
from OPSIONALdateFilter dari tanggal (format: YYYY-MM-DD)
to OPSIONALdateFilter sampai tanggal (format: YYYY-MM-DD)
status OPSIONALstringpending, completed, atau voided
per_page OPSIONALintegerJumlah item per halaman (default: 20)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
URL dengan Query Params
GET https://sapi-v1.obiwannn.web.id/api/v1/mobile/transactions?from=2026-06-01&to=2026-06-30&status=completed&per_page=10
Contoh Request (cURL)
curl "https://sapi-v1.obiwannn.web.id/api/v1/mobile/transactions?from=2026-06-01&status=completed&per_page=10" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
Response 200 OK
{
  "data": [
    {
      "id": 42,
      "code": "TRX-2026-001",
      "cashier": "Budi Santoso",
      "total_amount": 36000,
      "status": "completed",
      "order_type": "dine_in",
      "customer_name": null,
      "table_number": "A2",
      "created_at": "29/05/2026 14:30"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 5,
    "per_page": 10,
    "total": 48
  }
}
POST
/transactions/{transaction}/pay
Cashier / Owner

Membayar transaksi open bill yang masih berstatus pending. Setelah berhasil, status transaksi berubah menjadi completed.

Path Parameter
ParameterTipeKeterangan
transaction WAJIBintegerID transaksi
Request Body
FieldTipeKeterangan
payments WAJIBarrayMin 1 pembayaran
payments[].payment_method_id WAJIBintegerID metode pembayaran
payments[].amount WAJIBnumericNominal pembayaran (min 0)
payments[].reference_code OPSIONALstringKode referensi (maks 255)
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Content-Type: application/json
Request Body (JSON)
{
  "payments": [
    {
      "payment_method_id": 1,
      "amount": 40000
    }
  ]
}
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/transactions/42/pay \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "payments": [{ "payment_method_id": 1, "amount": 40000 }] }'
Response 200 OK
{
  "message": "Open bill berhasil dibayar.",
  "transaction_id": 42,
  "code": "TRX-2026-001",
  "total_amount": 36000,
  "change_amount": 4000,
  "status": "completed"
}
GET
/transactions/{transaction}/receipt
Bearer Token

Mengembalikan data struk lengkap: informasi tenant, detail transaksi, item dengan modifier, dan rincian pembayaran. Digunakan untuk mencetak struk.

price adalah harga yang dibayar per unit, original_price harga normal saat penjualan, dan discount_amount potongan per unit (0 bila tanpa potongan). Cetak baris berdiskon sebagai harga normal lalu baris Diskon. discount_total adalah jumlah potongan untuk baris "Anda hemat" — keterangan saja, subtotal sudah bersih dari potongan. original_price bernilai null pada transaksi tanpa jejak potongan (penjualan offline).

Path Parameter
ParameterTipeKeterangan
transaction WAJIBintegerID transaksi
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Response 200 OK
{
  "data": {
    "tenant": {
      "name": "SAPI Cafe",
      "address": "Jl. Sudirman No. 12",
      "phone": "+62812345678"
    },
    "transaction": {
      "code": "TRX-2026-001",
      "date": "29/05/2026 14:30",
      "cashier": "Budi Santoso",
      "total_amount": 36000,
      "discount_total": 0,
      "change_amount": 4000,
      "status": "completed",
      "order_type": "dine_in",
      "table_number": "A2",
      "notes": null,
      "is_open_bill": false
    },
    "items": [
      {
        "name": "Kopi Susu Regular",
        "qty": 2,
        "price": 18000,
        "original_price": 18000,
        "discount_amount": 0,
        "subtotal": 36000,
        "notes": "Gula sedikit",
        "modifiers": [
          { "name": "Extra Shot", "extra_price": 0 }
        ]
      }
    ],
    "payments": [
      { "method": "Tunai", "amount": 40000, "reference_code": null }
    ]
  }
}
POST
/transactions/{transaction}/void
Owner Only

Membatalkan (void) transaksi yang sudah berstatus completed. Hanya owner yang dapat melakukan ini. Stok akan dikembalikan secara otomatis.

Path Parameter
ParameterTipeKeterangan
transaction WAJIBintegerID transaksi yang akan di-void
Header Wajib
Authorization: Bearer {token}
Accept: application/json
Contoh Request (cURL)
curl -X POST https://sapi-v1.obiwannn.web.id/api/v1/mobile/transactions/42/void \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
Response 200 OK
{
  "message": "Transaksi berhasil di-void.",
  "transaction_id": 42,
  "code": "TRX-2026-001",
  "status": "voided"
}