Dutchie Inventory Intake via Backoffice API
Mint stores receive physical inventory every day. The canonical source of truth for what's on the shelf is Dutchie.
This course walks through the Backoffice REST intake flow end-to-end against the AZ sandbox (LocId 1989) — the same path production uses, with zero risk to real stock.
Learning objectives
- Authenticate against the Dutchie Backoffice REST API and reuse a cached session.
- Identify which of the three sandboxes to use for AZ-dispensary testing.
- Fetch the prerequisite IDs — vendor, room, product — that a receive call needs.
- POST an inventory receive with the correct field shape and explain why each field is required.
- Verify a receive materialized using the right read endpoints (and the subtle parameter gotcha).
- Diagnose the three most common failures: orphaned receives, empty reads, SQL errors.
Prerequisites: Familiarity with HTTP/REST and JSON. Access to Dutchie Backoffice credentials. Clone of letsgomint-us repo.
Source: docs/elearning/dutchie-inventory-intake-course.md
| Responsible | Juan Palomino |
|---|---|
| Last Update | 04/18/2026 |
| Completion Time | 50 minutes |
| Members | 2 |
Module 3 — Prerequisite IDs
Vendor, room, product
A receive call needs three IDs you cannot guess. Pull them first.
3.1 Vendor
const vendors = await client.post('vendor/get-vendors', { LocId: 1989 });
// [{ VendorId: 39195, VendorName: 'PALOMINO PRINTING', ... }]
Note the path has no v2/ prefix. Common misguess: v2/vendor/get-vendors returns 404.
3.2 Room
const rooms = await client.post('v2/room/get-rooms', { LocId: 1989 });
// [{ RoomId: 24772, RoomName: 'Sales Floor', PosRoom: 'yes', InventoryRoom: 'yes' }]
At the AZ sandbox there are 4 rooms. Pick one whose InventoryRoom === yes.
3.3 Product
const lite = await client.post('product-master/get-products-lite', { LspId: 575 });
// [{ ProductId: 11484902, Sku: '3B4791', Name: 'Tommy Chong Joint (H)', ... }]
get-products-lite is LSP-scoped (~17k products). For richer detail use get-product-master.
Vendor, room, product
A receive call needs three IDs you cannot guess. Pull them first.
3.1 Vendor
const vendors = await client.post('vendor/get-vendors', { LocId: 1989 });
// [{ VendorId: 39195, VendorName: 'PALOMINO PRINTING', ... }]
Note the path has no v2/ prefix. Common misguess: v2/vendor/get-vendors returns 404.
3.2 Room
const rooms = await client.post('v2/room/get-rooms', { LocId: 1989 });
// [{ RoomId: 24772, RoomName: 'Sales Floor', PosRoom: 'yes', InventoryRoom: 'yes' }]
At the AZ sandbox there are 4 rooms. Pick one whose InventoryRoom === yes.
3.3 Product
const lite = await client.post('product-master/get-products-lite', { LspId: 575 });
// [{ ProductId: 11484902, Sku: '3B4791', Name: 'Tommy Chong Joint (H)', ... }]
get-products-lite is LSP-scoped (~17k products). For richer detail use get-product-master.
When it breaks
| Symptom | Root cause | Fix |
|---|---|---|
Cannot insert the value NULL into column 'Status' | Missing header Status | Add Status: "Received" at top level |
Receive returns true, preorder/inventory unchanged | Missing RoomId per product | Add RoomId to every Products[] item |
get-packages-from-receive returns [] | Used Id instead of ReceiveInventoryHistoryId | Rename the param |
User is not permitted | Endpoint needs elevated permissions | Not fatal for intake — use scope-appropriate endpoints |
| Receive succeeds but location has no rooms | Using IL (2862) or OH (2869) sandbox | Switch to AZ sandbox (1989) |
Final assessment
Work through these against the AZ sandbox (LocId 1989). Use scripts/_probe/sandbox-az-retry.cjs as reference.
- Authenticate and confirm your session sees at least 18 locations.
- Pick a vendor, a room (
InventoryRoom === "yes"), and two cannabis products. - POST a receive for quantity 3 of each. Capture the returned
ReceiveInventoryHistoryId. - Verify: two new packages, history row with
Products: 2, both SKUs inpreorder/inventoryat Qty 3. - Summarize what you'd do if step 4c failed but 4a and 4b passed.
Grading rubric
- Steps 1-3 complete with
Result: true— basic pass. - Step 4 all green — working knowledge.
- Step 5 identifies that packages exist but are in a non-sellable room, and points to checking
RoomNameon package records — mastery.
Not done until you've read it back
5.1 Packages
POST api/v2/inventory/get-packages-from-receive
{ "LocId": 1989, "ReceiveInventoryHistoryId": 1379127 }
Use ReceiveInventoryHistoryId — not Id. Passing Id silently returns [] with no error.
Healthy response: one package per product line, each with a PackageId, matching SKU, and Quantity equal to what you sent.
5.2 Receive history
POST api/v2/inventory/get-receive-history
{ "LocId": 1989, "StartDate": "<ISO>", "EndDate": "<ISO>" }
Your new row has Status: "Received", your VendorName, OrderTitle, and Products count.
5.3 Sellable inventory
POST api/preorder/inventory
{ "LocId": 1989 }
Your SKUs appear with Quantity equal to what you received. If not — the packages exist but are in a non-sellable room.