Documentation Index
Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Three endpoints support atomic multi-item inventory operations. Each accepts an items[] array and guarantees all-or-nothing behavior: every item writes in a single database transaction. If item 5 of 10 fails, items 0-4 are automatically rolled back.
| Endpoint | Purpose | GL fires? |
|---|
POST /v1/inventory/receive | Non-PO receipts (FIFO layer creation per item) | No (not PO-linked) |
POST /v1/inventory/adjustments | Cycle counts, shrinkage, revaluations | Yes, per item in same transaction |
POST /v1/inventory/transfers | Inter-location transfers | No on draft; yes on submit |
Each endpoint also accepts single-item (top-level) payloads for backward compatibility.
POST /v1/inventory/receive
When to use
Use for non-PO standalone receipts — opening stock loads, vendor samples, or inventory counts that don’t correspond to a purchase order. Each item establishes its own FIFO cost layer. For PO-linked receipts use POST /v1/purchase-orders/{id}/receive.
Multi-item atomic receive
curl -s -X POST "https://api.arcuserp.com/v1/inventory/receive" \
-H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: receive-batch-20260513-001" \
-d '{
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"notes": "Restocking run from supplier",
"items": [
{
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"quantity": 10,
"unit_cost": 12.50,
"notes": "Circuit Breaker 10A White"
},
{
"product_id": "ec9288c3-12b1-474f-bb09-b0b15bb7a8ff",
"quantity": 5,
"unit_cost": 8.75,
"notes": "Square D Breaker Black"
}
]
}'
Response shape
{
"object": "inventory_receive_result",
"items": [
{
"index": 0,
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"quantity": 10,
"unit_cost": 12.5,
"balance": {
"id": "0118ff93-8f65-4a72-ae7e-ca2a75526631",
"on_hand": 10,
"allocated": 0,
"available": 10,
"total_value": "125.00",
"avg_unit_value": "12.5000"
},
"transaction": {
"id": "3096987e-b156-4ee0-b373-8056f5bd8d91",
"type": "receiving",
"quantity": 10,
"unit_price": "12.5000",
"total_price": "125.00",
"transaction_id": "IVT-0000153"
}
}
],
"summary": {
"total_items": 2,
"total_quantity": 15
}
}
Required fields per item
| Field | Type | Notes |
|---|
product_id | UUID | Required per item |
quantity | integer | Must be >= 1 |
unit_cost | number | Must be > 0. Establishes the FIFO cost layer. Zero-cost receives are rejected. |
location_id | UUID | Per-item override; falls back to root location_id |
notes | string | Optional. Per-item notes on the transaction record. |
Validation error shape (400)
If any item fails upfront validation, no items are written and the response includes exact param paths:
{
"error": "validation_error",
"code": "multi_item_validation_failed",
"type": "validation_error",
"hint": "One or more items failed validation. Fix all errors before retrying.",
"errors": [
{
"index": 0,
"param": "items[0].unit_cost",
"hint": "unit_cost must be a finite positive number. Standalone receive establishes FIFO cost basis; zero-cost receives corrupt downstream COGS."
}
]
}
POST /v1/inventory/adjustments
When to use
Use for cycle-count corrections, shrinkage write-downs, and inventory revaluations. A GL journal entry fires per item inside the same database transaction (Rule 21). Positive deltas (adding stock) require unit_cost to establish a new FIFO layer. Negative deltas consume existing FIFO layers automatically and calculate COGS from the oldest layers.
Multi-item atomic adjustment
curl -s -X POST "https://api.arcuserp.com/v1/inventory/adjustments" \
-H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: adj-cycle-count-20260513-001" \
-d '{
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"reason": "cycle_count",
"memo": "Monthly cycle count Q2 2026",
"items": [
{
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"quantity_delta": 3,
"unit_cost": 12.50,
"notes": "Count correction -- physical count higher than system"
},
{
"product_id": "ec9288c3-12b1-474f-bb09-b0b15bb7a8ff",
"quantity_delta": -2,
"notes": "Shrinkage discovered during count"
}
]
}'
Response shape
{
"object": "inventory_adjustment_result",
"items": [
{
"index": 0,
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"quantity_delta": 3,
"gl_amount": 37.5,
"journal_entry_id": "c79ab8cf-b840-471f-bea9-6a24df80f550",
"balance": {
"on_hand": 13,
"allocated": 0,
"available": 13,
"total_value": "162.50"
},
"transaction": {
"id": "10e06976-8096-48bc-bbbd-5fe71626f544",
"type": "adjustment",
"quantity": 3,
"unit_price": "12.5000",
"total_price": "37.50",
"transaction_id": "IVT-0000155"
},
"consumed_layers": []
},
{
"index": 1,
"product_id": "ec9288c3-12b1-474f-bb09-b0b15bb7a8ff",
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"quantity_delta": -2,
"gl_amount": -17.5,
"journal_entry_id": "a8c5c8c6-b0b0-4e63-918d-a247189735f3",
"balance": {
"on_hand": 3,
"allocated": 0,
"available": 3,
"total_value": "26.25"
},
"transaction": {
"id": "93659e88-f3ba-4709-9798-388886820ea0",
"type": "adjustment",
"quantity": -2,
"unit_price": "8.7500",
"total_price": "17.50",
"transaction_id": "IVT-0000156"
},
"consumed_layers": [
{
"layer_id": "3de7680e-fba2-444a-a204-7290cc6f08e1",
"qty_used": 2,
"qty_after": 3,
"unit_cost": 8.75,
"layer_cogs": 17.5
}
]
}
],
"summary": {
"total_items": 2,
"total_gl_impact": 20
}
}
Required fields per item
| Field | Type | Notes |
|---|
product_id | UUID | Required per item |
quantity_delta | integer | Non-zero signed integer. Positive = add stock, negative = remove stock. |
unit_cost | number | Required when quantity_delta > 0 (new FIFO layer). Ignored for negative deltas (FIFO auto-resolves). |
location_id | UUID | Per-item override; falls back to root location_id |
reason | string | Per-item override; falls back to root reason. Common values: cycle_count, shrinkage, revaluation, manual. |
notes | string | Optional. Combined with memo in the transaction record. |
GL behavior
Each adjustment item creates its own journal entry (DR/CR balanced to the cent):
- Positive delta (add stock): DR Inventory Asset, CR Inventory Adjustment/Suspense. Amount =
quantity_delta * unit_cost.
- Negative delta (remove stock): DR Inventory Adjustment/Shrinkage, CR Inventory Asset. Amount = FIFO COGS (oldest layers consumed first).
The journal_entry_id on each item links the transaction to the GL record. gl_amount is positive for additions, negative for removals.
Serialized product adjustments
For serialized products, include matching serial arrays whose length equals abs(quantity_delta):
{
"product_id": "SERIALIZED_PRODUCT_UUID",
"quantity_delta": -1,
"serial_numbers_removed": ["SN-ABC-001"],
"notes": "Damaged unit removed from stock"
}
POST /v1/inventory/transfers
When to use
Use to move inventory between locations within the same entity (internal transfer) or between entities (intercompany transfer). A transfer is created in draft status. GL entries fire when the transfer is submitted (internal) or shipped (intercompany).
Multi-item atomic transfer
curl -s -X POST "https://api.arcuserp.com/v1/inventory/transfers" \
-H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: xfer-replenish-20260513-001" \
-d '{
"source_location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"dest_location_id": "828cabe6-3fa2-4fb7-a2af-2280bbaba335",
"notes": "Weekly replenishment transfer to secondary warehouse",
"items": [
{
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"quantity": 5,
"notes": "Circuit Breaker 10A White"
},
{
"product_id": "ec9288c3-12b1-474f-bb09-b0b15bb7a8ff",
"quantity": 3,
"notes": "Square D Breaker Black"
}
]
}'
Response shape
{
"data": {
"id": "e1ce42dc-e9ae-40c4-a923-aa93100b0947",
"entity_from_id": "65dcd234-53c3-4f54-b772-ee349528d497",
"entity_to_id": "65dcd234-53c3-4f54-b772-ee349528d497",
"location_from_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"location_to_id": "828cabe6-3fa2-4fb7-a2af-2280bbaba335",
"transfer_number": "SB-TRF-0000001",
"status": "draft",
"transfer_type": "internal",
"notes": "Weekly replenishment transfer to secondary warehouse",
"gl_posted": false,
"items": [
{
"id": "d8c6bb7a-9c4c-446a-8518-d6f9398dbedc",
"transfer_id": "e1ce42dc-e9ae-40c4-a923-aa93100b0947",
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"quantity_sent": "5.0000",
"quantity_received": "0.0000",
"notes": "Circuit Breaker 10A White"
}
]
}
}
Field name aliases
Both naming conventions are accepted:
| Canonical | Alias |
|---|
source_location_id | from_location_id |
dest_location_id | to_location_id |
GL lifecycle
| Status | GL action |
|---|
draft | No GL. Items stored; no stock movement yet. |
submitted (internal) | GL fires: DR Transit Inventory / CR Source Warehouse Inventory. |
shipped (intercompany) | GL fires: DR Due From / CR Source Inventory. |
received (intercompany) | GL fires: DR Dest Inventory / CR Due To. |
Atomicity guarantee
All three endpoints wrap their items[] loop in a single pool.connect() + BEGIN/COMMIT/ROLLBACK transaction:
- All items validate upfront (before
BEGIN).
BEGIN starts.
- Each item writes inside the transaction.
- Any item failure triggers
ROLLBACK — every item reverts.
COMMIT fires only after all items succeed.
On failure, the response includes failed_item_index and items_completed_before_failure so you know which item triggered the rollback. All items are reverted regardless of items_completed_before_failure — the count is informational only.
Idempotency
All three endpoints honor the Idempotency-Key header. Replaying the same key within 24 hours returns the original response without re-applying the operation. Use a UUID or a deterministic key tied to your batch (e.g. receive-batch-${date}-${batchId}).
POST /v1/purchase-orders/{id}/receive — PO-linked receipts (triggers GRNI GL, matches against PO lines)
GET /v1/inventory/balances — query current on_hand / allocated / available by product and location
GET /v1/inventory/transactions — movement history log
GET /v1/inventory/fifo-layers — FIFO cost layers per product/location
POST /v1/inventory/serials — bulk enroll serial numbers (status transition only, no on_hand change)