Skip to main content

The Xagle Inventory API

The Xagle Inventory API lets you programmatically manage items, stock levels, adjustments, physical counts, purchase order receiving, and units of measure. It is designed around stock reconciliation: pull system quantities, compare against a physical count, review variances, and post the true-up in one atomic batch.

A ready-to-import Postman collection covers every endpoint on this page (plus the rest of the Xagle API).

Authentication & Conventions

Authentication works exactly like the rest of the Xagle API: POST /api/v1/login with your email and password returns a token; send it on every request as Authorization: Bearer {token}.

Responses use the standard envelope: {"success": true, "message": "...", "data": ...} on success and {"success": false, "message": "..."} on failure. Validation failures return {"errors": {"field": ["message"]}} with HTTP 422.

CodeMeaning
200Read or update succeeded
201Record created / adjustment or receipt posted
404Record not found
422Validation or business-rule failure (e.g. over-receiving a PO line)
409Duplicate request with the same Idempotency-Key still in flight
400Other errors

Pagination: list endpoints paginate by default. Use page and per_page (maximum 200) to page through results; the standard paginator object is returned under data.

Dates: any standard date format is accepted; ISO 8601 (2026-08-04 or 2026-08-04T14:30:00Z) is recommended.

Quantities: every quantity is expressed in the item's unit of measure, which is included as uom wherever quantities appear.

Items

OperationEndpoint
List (filterable)GET /api/v1/items
ShowGET /api/v1/items/{id}
Lookup by SKUGET /api/v1/items/sku/{sku}
CreatePOST /api/v1/items
UpdatePUT /api/v1/items/{id}
DeleteDELETE /api/v1/items/{id}
Bulk create/update by SKUPOST /api/v1/items/bulk
Kit build capacityGET /api/v1/items/{id}/buildable

Writable fields: name (required), sku (unique, max 50), description, category_id, unit_of_measure_id, price (selling price), standard_cost, stockable, kit_item_type (no / requires_pre_building / explodes_on_invoice), type (product / service), status, source_method (make / purchase), min_stock_qty (reorder point), max_stock_qty, reorder_qty, primary_vendor_id, default_warehouse_id, lead_time, inventory_notes, company_profile_id, tax_rate_id, tax_rate_2_id.

Important: qty_on_hand is accepted on create only, as an opening balance (it writes an opening movement to the ledger). After creation, stock changes only through adjustments, receipts, and counts — a qty_on_hand value in a PUT body is ignored. Updates replace the fields you send and leave omitted fields unchanged.

Deleting an item is refused (400) when it is used as a kit component or has stock movement history — set its status to retired instead.

Show response embeds the category, unit of measure, default vendor, custom fields, kit components, and a stock block:

{
  "success": true,
  "message": "Record retrieved successfully",
  "data": {
    "item": { "id": 30, "name": "Slewing Drive", "sku": "SLEW-9", "stockable": 1, ... },
    "stock": {
      "quantity_on_hand": 10,
      "quantity_committed": 0,
      "quantity_on_order": 6,
      "quantity_available": 10,
      "quantity_backordered": 0,
      "reorder_point": 2,
      "reorder_quantity": 0,
      "uom": "Each",
      "last_movement_at": "2026-08-04 14:12:13",
      "warehouses": []
    }
  }
}

quantity_committed counts unfulfilled quantities on sent invoices; quotes do not reserve stock. quantity_available = on hand − committed.

Bulk upsert (max 200 rows) is keyed on SKU and returns per-row outcomes instead of failing the whole batch:

POST /api/v1/items/bulk
{ "items": [ { "sku": "WIDGET-1", "name": "Widget", "stockable": 1, "qty_on_hand": 10 } ] }

HTTP 200
{ "success": true, "data": [ { "index": 0, "sku": "WIDGET-1", "action": "created", "id": 31 } ] }

Buildable reports how many finished units of a kit the current component stock supports, and which component is the binding constraint. Pass ?basis=available to use available (on hand − committed) instead of on hand:

GET /api/v1/items/33/buildable

HTTP 200
{
  "success": true,
  "data": {
    "units_buildable": 4,
    "basis": "on_hand",
    "binding_constraint": { "sku": "HOSE-KIT", "required_per_unit": 1, "quantity_on_hand": 4, "supports_units": 4 },
    "components": [
      { "sku": "STEEL-KIT", "required_per_unit": 2, "quantity_on_hand": 10, "quantity_on_order": 0, "supports_units": 5 },
      { "sku": "HOSE-KIT",  "required_per_unit": 1, "quantity_on_hand": 4,  "quantity_on_order": 0, "supports_units": 4 }
    ]
  }
}

Stock Levels

OperationEndpoint
Snapshot of all tracked itemsGET /api/v1/inventory
One item, with warehouse breakdownGET /api/v1/inventory/{item_id}
Full export for reconciliationGET /api/v1/inventory/export?format=json|csv
Open PO quantities by itemGET /api/v1/inventory/on-order

Snapshot filters: item_id, sku, low_stock=1, nonzero=1, include_inactive=1. Each row contains item_id, sku, name, quantity_on_hand, quantity_committed, quantity_on_order, quantity_available, quantity_backordered, reorder_point, reorder_quantity, uom, and last_movement_at.

/inventory/export returns the same rows unpaginated; format=csv downloads a CSV file. This is the recommended starting point for a reconciliation script.

/inventory/on-order requires the Purchase Orders add-on and returns per-item ordered / received / outstanding totals with a per-line breakdown of every open PO.

Stock Adjustments

OperationEndpoint
Post an adjustmentPOST /api/v1/inventory/adjustments
List adjustmentsGET /api/v1/inventory/adjustments

Body: identify the item by item_id or sku, then send exactly one of quantity_delta (relative change, may be negative) or quantity_absolute (set-to value, for physical counts). reason_code is required: physical_count, damage, shrinkage, found, correction, scrap, transfer_in, transfer_out, or other. Optional: notes, reference (free-text link to a count sheet or document), effective_date, location_id.

POST /api/v1/inventory/adjustments
Idempotency-Key: count-2026-08-04-line-17
{ "sku": "SLEW-9", "quantity_delta": -2, "reason_code": "damage", "notes": "cracked in staging" }

HTTP 201
{
  "success": true,
  "message": "Adjustment successfully posted.",
  "data": {
    "movement": { "id": 812, "tran_type": "issue", "qty": "2.0000", "reason_code": "damage", ... },
    "quantity_on_hand": 8,
    "no_change": false
  }
}
  • Adjustments are immutable ledger entries — there is no update or delete. Corrections are new offsetting adjustments.
  • An absolute set equal to the current on-hand returns 200 with "no_change": true and writes nothing.
  • Concurrent adjustments are safe: the delta for an absolute set is computed under a database row lock.
  • If negative stock is disabled for the account, a movement that would take on-hand below zero returns 422 with a clear message.

Idempotency: send an Idempotency-Key header (up to 80 characters) on adjustment and PO receipt POSTs. Retrying with the same key and payload replays the original stored response (marked with the Idempotency-Replayed: true header) without posting again. The same key with a different payload is rejected with 422, and a duplicate arriving while the first request is still processing receives 409. Failed attempts release the key so the request can be retried.

List filters: item_id, reason_code, user_id, from_date, to_date.

Movement Ledger

GET /api/v1/inventory/movements returns every stock movement from every source — receipts, invoice shipments, adjustments, kit builds, transfers, web store orders. The ledger is append-only by design: there are no update or delete endpoints.

Filters: item_id or sku, warehouse_id, type (receipt / issue), source_type (adjustment, invoice, inventory_receipt, kit_build, transfer, po, web_store_order), source_id, reason_code, user_id, from_date, to_date.

Each row includes signed_quantity (negative for issues), unit_cost, reference, and a source_type / source_id pair linking it to the document that caused it.

Physical Count Sessions

The endpoint set that makes a real true-up possible: snapshot the system quantities, enter physical counts, review variances, then post once — atomically.

OperationEndpoint
List sessionsGET /api/v1/inventory/counts
Open a sessionPOST /api/v1/inventory/counts
Show a sessionGET /api/v1/inventory/counts/{id}
Submit counted quantities (batch)POST /api/v1/inventory/counts/{id}/lines
Variance reportGET /api/v1/inventory/counts/{id}/variances
Post the sessionPOST /api/v1/inventory/counts/{id}/post

Open: {"name": "August count", "item_ids": [30, 31], "variance_threshold_pct": 50}. Scope is either an explicit item_ids list, or all active tracked items (optionally narrowed by category_id / warehouse_id). Each line snapshots the system quantity at open as expected_qty.

Submit counts: lines may reference line_id, item_master_id, or sku; resubmitting a line overwrites its count:

POST /api/v1/inventory/counts/3/lines
{ "lines": [ { "sku": "SLEW-9", "counted_qty": 11 }, { "sku": "HOSE-KIT", "counted_qty": 4 } ] }

Review: the variance report returns uncounted_lines, variance_lines, pending_approval, and each line's expected / counted / variance figures — so a human can review before committing.

Post: commits every variance as a physical_count adjustment and closes the session in a single transaction — partial posting cannot happen. Posting is blocked while any line is uncounted, or while variances above the session threshold are unapproved (send {"approve_all": 1} to approve them in the same call). A completed session cannot be posted twice.

Purchase Orders & Receiving

Available when the Purchase Orders add-on is enabled.

OperationEndpoint
ListGET /api/v1/purchase-orders
Show (with line statuses)GET /api/v1/purchase-orders/{id}
CreatePOST /api/v1/purchase-orders
Update headerPUT /api/v1/purchase-orders/{id}
DeleteDELETE /api/v1/purchase-orders/{id}
Add line itemsPOST /api/v1/purchase-orders/{id}/items
Delete a line itemDELETE /api/v1/purchase-orders/{id}/items/{itemId}
Receive against the POPOST /api/v1/purchase-orders/{id}/receipts
Receipt historyGET /api/v1/purchase-orders/{id}/receipts

List filters: status (draft, sent, approved, paid, rejected, canceled), vendor_id, number, from_date, to_date.

Create: send vendor_id or vendor_name (a new vendor is created if the name is unknown), a required purchase_order_date, and optionally expected_delivery, client_id, company_profile_id, terms, footer, summary, and inline items. Each item takes item_master_id and/or name, a quantity, and an optional cost (defaulting to the item's most recent cost). Totals are calculated automatically.

Receiving supports partial receipts and multiple receipts per PO, and accepts the Idempotency-Key header:

POST /api/v1/purchase-orders/12/receipts
Idempotency-Key: receipt-po12-2026-08-04
{
  "lines": [ { "po_line_id": 40, "quantity_received": 6 } ],
  "received_on": "2026-08-04",
  "tracking_number": "1Z999"
}

HTTP 201
{
  "success": true,
  "message": "Receipt successfully posted.",
  "data": {
    "receipt": { "id": 7, "received_on": "2026-08-04 00:00:00", ... },
    "lines": [
      { "po_line_id": 40, "quantity_ordered": 10, "quantity_received": 6, "quantity_outstanding": 4, "status": "partial" }
    ],
    "is_fully_received": false
  }
}
  • Receiving more than the ordered quantity on a line is rejected with 422.
  • Each receipt posts receipt movements to the stock ledger (source_type: "po"), increasing on-hand for tracked items.
  • Line status is open, partial, or received; use is_fully_received for the whole PO.
  • Deleting a PO (or a PO line) that has posted receipts is refused — reverse the receipt first from the PO screen.

Units of Measure

OperationEndpoint
ListGET /api/v1/uoms
ShowGET /api/v1/uoms/{id}
CreatePOST /api/v1/uoms
UpdatePUT /api/v1/uoms/{id}
DeleteDELETE /api/v1/uoms/{id}

Fields: name (unique) and decimal_places (0–4, display precision). A unit of measure assigned to items cannot be deleted. Units are labels with a display precision — there are no unit conversions, so every quantity you read or write is in the item's own unit.

Reconciliation Walkthrough

  1. GET /api/v1/inventory/export — pull current system quantities keyed by SKU.
  2. POST /api/v1/inventory/counts — open a count session scoped to the items you are counting; the system quantities are snapshotted.
  3. POST /api/v1/inventory/counts/{id}/lines — submit physical counts by SKU, in batches, as they come in.
  4. GET /api/v1/inventory/counts/{id}/variances — review every variance before committing anything.
  5. POST /api/v1/inventory/counts/{id}/post — commit all variances in one atomic batch. Every change lands on the movement ledger with reason physical_count.
  6. GET /api/v1/items/{id}/buildable — see how many finished units your reconciled component stock supports, and which component is the constraint.