---
title: "API Gudang (Warehouse)"
summary: "Endpoint /warehouse: katalog barang, stok per cabang sebagai snapshot, dan riwayat barang masuk/keluar beserta cara merekonsiliasinya."
slug: developer-api-gudang
product: LENSIRO
source: https://lensiro.com/dokumentasi/developer-api-gudang
updated: 2026-08-23
---

# API Gudang (Warehouse)

> Endpoint /warehouse: katalog barang, stok per cabang sebagai snapshot, dan riwayat barang masuk/keluar beserta cara merekonsiliasinya.

Domain gudang punya satu keanehan yang perlu dipahami lebih dulu: **Lensiro tidak menyimpan angka stok sebagai kolom.** Stok selalu dihitung ulang dari seluruh riwayat pergerakan barang. Itu membuat angkanya selalu konsisten dengan riwayat, tapi juga membuat stok tidak bisa disinkronkan secara inkremental seperti data lain.

## `GET /warehouse/items` — katalog barang

```bash
curl -H "Authorization: Bearer $KEY" "$BASE/warehouse/items?category_id=1&limit=200"
```

```json
{
  "id": 3321,
  "code": "FR3321",
  "custom_code": "8991234567890",
  "display_code": "8991234567890",
  "name": "Frame CONTOH X-01 Black",
  "category": { "id": 1, "code": "FR", "name": "Frame" },
  "sub_category": { "id": 4, "name": "Full Rim" },
  "brand": { "id": 9, "name": "CONTOH" },
  "sub_brand": { "id": 21, "name": "X Series" },
  "color": { "id": 2, "name": "Black" },
  "sub_color": null,
  "prescription": { "spherical": "-2.00", "cylinder": "-0.50", "addition": null },
  "unit": "pcs",
  "sell_price": 1000000,
  "expired_date": null,
  "memo": null,
  "created_at": "2025-03-01T00:00:00.000Z",
  "updated_at": "2026-08-01T00:00:00.000Z",
  "deleted_at": null
}
```

| Field | Catatan |
|---|---|
| `id` | **Kunci upsert.** Satu-satunya pengenal yang tidak pernah berubah |
| `code` | Kode internal Lensiro. **Bisa berubah** kalau kategori barang diganti |
| `custom_code` | Barcode toko atau kode dari sistem lama; `null` kalau tidak diisi |
| `display_code` | `custom_code` bila ada, kalau tidak `code` — inilah yang tercetak di label barcode |
| `category` sampai `sub_color` | Klasifikasi barang; masing-masing bisa `null` |
| `prescription` | Ukuran lensa (`spherical`, `cylinder`, `addition`) sebagai teks; semuanya `null` untuk barang non-lensa |
| `unit` | `"pcs"` atau `null` |
| `sell_price` | Harga jual standar, Rupiah bulat |
| `expired_date` | Tanggal kedaluwarsa untuk barang yang punya, misalnya cairan lensa kontak |

> **Selalu upsert berdasarkan `id`.** Kalau Anda memakai `code` sebagai kunci, katalog Anda akan pecah begitu ada barang yang pindah kategori. Harga modal atau HPP sengaja tidak diekspos di v1.

Detail satu barang: `GET /warehouse/items/{id}`.

## `GET /warehouse/items/{id}/stocks`

Stok satu barang di seluruh cabang. Tidak berpaginasi (jumlah barisnya sebanyak cabang) dan dibungkus `{ "data": [...] }`. Bentuk barisnya sama dengan `/warehouse/stocks` di bawah.

## `GET /warehouse/stocks` — snapshot stok

Stok terkini per pasangan **(barang, cabang)**.

```bash
curl -H "Authorization: Bearer $KEY" "$BASE/warehouse/stocks?branch_id=2&limit=500"
```

```json
{
  "item_id": 3321,
  "item_code": "FR3321",
  "item_name": "Frame CONTOH X-01 Black",
  "branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
  "on_hand": 4,
  "as_of": "2026-08-16T02:00:00.000Z"
}
```

Yang perlu diketahui:

- **Ini snapshot, bukan data yang bisa disinkronkan inkremental.** Tidak ada `updated_since` dan tidak ada `deleted_at` — bukan karena terlewat, tapi karena angka stok tidak punya "waktu terakhir berubah" yang bisa dipercaya. Menyediakan `updated_since` di sini akan jadi janji palsu.
- **Cara pakainya: tarik ulang seluruh snapshot secara berkala**, lalu timpa tabel stok Anda. Jangan mencoba men-*diff* atau menambahkan selisih.
- `as_of` adalah waktu server saat snapshot dihitung. Simpan nilai ini supaya Anda tahu seberapa baru angkanya.
- Barang nonaktif, yang sudah dihapus dari katalog, tidak muncul.
- Barang yang tidak punya catatan stok sama sekali di sebuah cabang tidak menghasilkan baris — anggap tidak ada baris sama dengan nol.
- `on_hand` bisa **negatif** kalau ada kesalahan pencatatan di lapangan, misalnya barang terjual sebelum penerimaan dicatat. Jangan asumsikan selalu nol atau lebih.

## `GET /warehouse/stock-movements` — barang masuk & keluar

Ledger pergerakan stok, satu baris per kejadian. Ini cerminan laporan Perubahan Stok, dan sumber angka yang menjelaskan **kenapa** stok bergerak.

```bash
curl -H "Authorization: Bearer $KEY" \
  "$BASE/warehouse/stock-movements?occurred_from=2026-08-01T17:00:00Z&item_id=3321"
```

```json
{
  "id": "sale:90101",
  "type": "SALE",
  "quantity_change": -1,
  "item": { "id": 3321, "code": "FR3321", "name": "Frame CONTOH X-01 Black" },
  "branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
  "reference": {
    "sale_id": 18234,
    "sales_order_number": "SO-PST-1042",
    "invoice_number": "INV-PST-987"
  },
  "memo": null,
  "occurred_at": "2026-08-13T02:11:40.000Z"
}
```

- `quantity_change` **bertanda**: positif berarti stok bertambah, negatif berarti stok berkurang.
- `id` berbentuk `"{jenis}:{nomor}"`, misalnya `sale:90101` atau `purchase-receive:8812`. Bentuknya string, bukan angka — pakai apa adanya sebagai kunci upsert.

### Jenis pergerakan

| `type` | Tanda | Arti | Isi `reference` |
|---|---|---|---|
| `PURCHASE_RECEIVE` | + | Penerimaan barang dari pembelian | `purchase_receive_id` |
| `TRANSFER_IN` | + | Mutasi masuk dari cabang lain, setelah diterima | `stock_mutation_id` |
| `ADJUSTMENT` | +/− | Penyesuaian stok opname atau penghapusan | `adjustment_job_id`, `adjustment_type` |
| `SALE` | − | Barang terjual | `sale_id`, `sales_order_number`, `invoice_number` |
| `COMP_ITEM` | − | Barang bonus atau komplimen dalam transaksi | `sale_id`, `sales_order_number`, `invoice_number` |
| `PURCHASE_RETURN` | − | Retur barang ke supplier | `purchase_return_id` |
| `PURCHASE_SENT` | − | Barang dikirim untuk pesanan pembelian | `purchase_order_id` |
| `TRANSFER_OUT` | − | Mutasi keluar ke cabang lain | `stock_mutation_id` |
| `FIT_BUFFER` | − | Barang dipesan lewat Lensiro Fit | `fit_order_id` |

Nilai-nilai itu juga yang diterima parameter `type`. Untuk `ADJUSTMENT`, `adjustment_type` bernilai `OPNAME` atau `WRITE_OFF`.

### Catatan sinkronisasi

Endpoint ini sengaja **tidak** punya `updated_since`, karena pergerakan stok bukan data yang hanya bertambah:

- **`TRANSFER_OUT` muncul saat barang dikirim; `TRANSFER_IN` baru muncul setelah cabang tujuan mengonfirmasi penerimaan.** Jadi selama barang di perjalanan, stok berkurang di cabang asal tanpa bertambah di cabang tujuan — itu perilaku yang benar, bukan bug.
- Waktu `occurred_at` untuk `TRANSFER_IN` memakai waktu perubahan terakhir dokumen mutasi, karena waktu penerimaan tidak disimpan terpisah. Nilainya bisa bergeser kalau dokumen mutasi diedit.
- Penyesuaian opname bisa diubah setelah dibuat.

**Pola yang disarankan:** tarik ulang **jendela 7 hari terakhir** secara berkala, misalnya tiap jam, lalu upsert berdasarkan `id`. Untuk backfill awal, tarik per rentang bulanan dengan `occurred_from` dan `occurred_to`.

### Rekonsiliasi

Jumlah `quantity_change` seluruh pergerakan untuk satu pasangan (barang, cabang) sama dengan `on_hand` di `/warehouse/stocks` — keduanya dihitung dari sumber yang sama. Kalau angka Anda tidak cocok, penyebab yang paling mungkin:

1. Rentang `occurred_from` / `occurred_to` memotong sebagian riwayat. Jumlah pergerakan sebagian tidak akan pernah sama dengan stok total.
2. Paginasi berhenti sebelum `has_more` bernilai `false`.
3. Snapshot ditarik pada waktu berbeda dengan pergerakan; bandingkan `as_of` dengan `occurred_at` terakhir.

> **Untuk angka stok yang dipakai mengambil keputusan, `/warehouse/stocks` adalah sumber kebenarannya.** Pakai `/warehouse/stock-movements` untuk menjelaskan pergerakan dan menyusun kartu stok, bukan untuk menghitung saldo sendiri.


---

Dokumentasi Lensiro · https://lensiro.com/dokumentasi/developer-api-gudang · diperbarui 23 Agustus 2026
