curl --request POST \
--url https://api.arcuserp.com/v1/inventory/opening-stock \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"target_on_hand": 144,
"unit_cost": 44,
"external_id": "VERSA-SNAPSHOT-99127",
"notes": "Opening stock from Versa snapshot (Dayton facility)"
}
'{
"object": "inventory_opening_stock_result",
"action": "noop",
"idempotent": true,
"balance": {},
"transaction": {},
"fifo_layer": {}
}{
"object": "inventory_opening_stock_result",
"action": "created",
"idempotent": true,
"balance": {},
"transaction": {},
"fifo_layer": {}
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "opening_stock_already_consumed",
"code": "opening_stock_already_consumed",
"hint": "<string>",
"consumed_qty": 123,
"opening_qty": 123
}Set opening on-hand for a (product, variant, location) -- REPLACE semantics, migration-only
NEW-GAP-MIGRATION-PHASE-4-NOT-IDEMPOTENT-P0 (2026-05-18). Canonical REPLACE-NOT-ADD opening-balance writer for one-shot migration loads.
Routes to utils/inventory-helpers.mjs::setOpeningStock, which sets
inventory_balances.on_hand = target_on_hand directly (NOT add a
delta). Re-running with the same input is a no-op (idempotent);
re-running with a different target replaces the prior opening row
- FIFO layer + balance. Re-running after the layer has been sold
/ transferred refuses with 409
opening_stock_already_consumed.
Scope: migration:write only. Live inventory edits MUST use
POST /v1/inventory/adjustments (delta semantics).
GL: NO journal entry posted. Opening Inventory Asset balance lands
via gl_accounts.opening_balance column write in Phase 5B of the
WSS migration. Posting a JE here would double-count.
Idempotency: Idempotency-Key header honored for double-click
protection. Payload-equality short-circuit inside setOpeningStock:
a re-run with the same target_on_hand + unit_cost returns
HTTP 200 + { idempotent: true, action: 'noop' } without any
DB write. A re-run with a different target returns HTTP 201 +
{ idempotent: false, action: 'replaced' }.
curl --request POST \
--url https://api.arcuserp.com/v1/inventory/opening-stock \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"product_id": "7f6f3e14-eaad-4c45-b922-209f6dc6d140",
"location_id": "91aa7588-4dc7-4edd-b172-7de91f5e1f78",
"target_on_hand": 144,
"unit_cost": 44,
"external_id": "VERSA-SNAPSHOT-99127",
"notes": "Opening stock from Versa snapshot (Dayton facility)"
}
'{
"object": "inventory_opening_stock_result",
"action": "noop",
"idempotent": true,
"balance": {},
"transaction": {},
"fifo_layer": {}
}{
"object": "inventory_opening_stock_result",
"action": "created",
"idempotent": true,
"balance": {},
"transaction": {},
"fifo_layer": {}
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "not_found",
"code": "not_found",
"type": "not_found",
"hint": "The requested order does not exist or does not belong to this entity.",
"param": "expand[0]",
"required": "accounts:read",
"request_id": "req_abc123"
}{
"error": "opening_stock_already_consumed",
"code": "opening_stock_already_consumed",
"hint": "<string>",
"consumed_qty": 123,
"opening_qty": 123
}Authorizations
API key issued per entity via Settings > Developers > API Keys.
Each key carries scopes (e.g. orders:read, products:write).
Bearer token format: Authorization: Bearer ark_live_ent_Test keys use ark_test_ent_. Both are issued per entity
via Settings > Developers > API Keys.
Headers
Client-generated unique key for idempotent POST/PATCH/DELETE operations. Alias for the Idempotency parameter. Max 255 chars. On retry with the same key, the original response is returned without re-executing the operation. Keys expire after 24 hours.
255Body
Arcus product UUID.
Arcus location UUID.
The TARGET on-hand value (not a delta). setOpeningStock writes
inventory_balances.on_hand = target_on_hand directly.
x >= 0Optional product_variants UUID; null for non-variant products.
Per-unit cost. Required when target_on_hand > 0; ignored when target_on_hand = 0. setOpeningStock refuses positive opening qty without a positive unit_cost (would corrupt FIFO + GL opening Inventory Asset balance).
x >= 0.0001Optional source-system row identifier (e.g., Versa snapshot id).
Stored on inventory_transactions.reference_number for audit
traceability. Idempotency keys on (entity, product, location,
reference_type='opening_balance'), NOT on external_id; external_id
is informational only.
80Optional free-text source memo. Stored on inventory_transactions.notes.
Response
Idempotent no-op. A prior opening row exists at this (entity, product, variant, location) with the same target_on_hand and unit_cost. No DB write occurred. Returns the existing balance
- transaction.
Was this page helpful?

