API docs

Purchase orders

Permission: purchase_orders (read / write). Everything is filtered to the locations the key can see.

With write access, an outside system (a channel manager like Flxpoint, a distributor portal) runs the whole buying cycle: create a draft, edit it, submit it to the vendor, cancel it, and receive stock against it. The rules match the back office.

Endpoint Permission
GET /v1/purchase-orders read
GET /v1/purchase-orders/{id} read
POST /v1/purchase-orders write
PATCH /v1/purchase-orders/{id} write
POST /v1/purchase-orders/{id}/submit write
POST /v1/purchase-orders/{id}/cancel write
POST /v1/purchase-orders/{id}/receive write (adds stock)

The purchase order object

{
  "object": "purchase_order",
  "id": 77,
  "number": "PO93-00019",
  "status": "partial",
  "location_id": 93,
  "vendor_id": 155,
  "vendor_reference": "LIP-SO-22817",
  "flxpoint_order_id": "FXP-448120",
  "ordered_on": "2026-09-20",
  "expected_on": "2026-09-26",
  "subtotal_cents": 263400,
  "shipping_cents": 0,
  "total_cents": 263400,
  "lines": [
    { "object": "purchase_order_line", "id": 512, "product_id": 2053, "vendor_sku": "GLPA195S203", "description": "Glock 19 Gen 5 9mm", "qty_ordered": 6, "qty_received": 4, "unit_cost_cents": 43900 }
  ],
  "created_at": "2026-09-20T14:00:00Z",
  "updated_at": "2026-09-24T17:30:00Z"
}

status is one of:

Status Meaning
draft Being built. Vendor, lines and header fields can all change.
submitted Sent to the vendor. Vendor and lines are frozen; header fields (dates, shipping, references, notes) can still change.
partial Some stock has been received.
received Everything ordered has been received.
closed Closed in Iron Register (e.g. the vendor shorted the rest).
cancelled Cancelled before anything was received.

List and get

GET /v1/purchase-orders. Filters: status, vendor_id, flxpoint_order_id, vendor_reference, number, location_id, updated_since, limit, cursor.

GET /v1/purchase-orders/{id} returns one purchase order with its lines.

curl https://app.ironregister.com/api/v1/purchase-orders?flxpoint_order_id=FXP-448120 \
  -H "Authorization: Bearer ir_live_…"

Create

POST /v1/purchase-orders. Send an Idempotency-Key so a retried request never creates two purchase orders.

Field
vendor_id Required. From GET /v1/vendors.
location_id Where the stock will be received. Defaults to the key's only location, else the shop's default location.
lines Required. 1–500 lines: { product_id or sku, qty, unit_cost_cents?, vendor_sku? }. unit_cost_cents defaults to the product's cost; vendor_sku to the product's vendor SKU.
expected_on YYYY-MM-DD
shipping_cents Added to the total
vendor_reference The vendor's order or confirmation number
flxpoint_order_id Your id for this purchase order. Must be unique in the shop: a second create with the same one is 409 DUPLICATE_PURCHASE_ORDER.
notes Saved after a "Via API (key name)" line
submit true to create and submit in one call
curl -X POST https://app.ironregister.com/api/v1/purchase-orders \
  -H "Authorization: Bearer ir_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: po-FXP-448120" \
  -d '{
    "vendor_id": 155,
    "location_id": 93,
    "flxpoint_order_id": "FXP-448120",
    "vendor_reference": "LIP-SO-22817",
    "expected_on": "2026-10-01",
    "lines": [
      { "sku": "AMM-9-115-50", "qty": 40, "unit_cost_cents": 1100 },
      { "sku": "GLK-G19G5", "qty": 2 }
    ],
    "submit": true
  }'

Returns 201 with the purchase order (status submitted here, draft without submit).

Update

PATCH /v1/purchase-orders/{id}. Send only what changes.

  • Header fields (expected_on, ordered_on, shipping_cents, vendor_reference, flxpoint_order_id, notes) can change while the purchase order is draft or submitted.
  • vendor_id and lines can change only while it's draft. Sending either on a submitted purchase order is 409 PO_SUBMITTED: the vendor already has it. Cancel it and create a new one to change what was ordered.
  • lines replaces every line.
  • Anything received, closed or cancelled is 409 INVALID_STATE.
curl -X PATCH https://app.ironregister.com/api/v1/purchase-orders/77 \
  -H "Authorization: Bearer ir_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "expected_on": "2026-10-03", "vendor_reference": "LIP-SO-22817-R1" }'

Submit

POST /v1/purchase-orders/{id}/submit moves a draft to submitted and stamps ordered_on (today, unless already set).

  • Anything but a draft is 409 INVALID_STATE.
  • A draft with no lines is 422 EMPTY_PO.

Cancel

POST /v1/purchase-orders/{id}/cancel cancels a draft or submitted purchase order.

  • Once anything has been received (partial), it can't be cancelled (409 INVALID_STATE). Close it in Iron Register instead.
  • received, closed and cancelled are 409 INVALID_STATE.

Receive

POST /v1/purchase-orders/{id}/receive records stock arriving against a submitted or partial purchase order and posts it at once:

  • Stock goes up at the purchase order's location, as receive movements.
  • Each line's qty_received goes up, and the status becomes partial or received.
  • The receiving.posted and inventory.changed webhooks fire.

Send an Idempotency-Key: a retried receive without one would add the stock twice.

Specific lines:

curl -X POST https://app.ironregister.com/api/v1/purchase-orders/77/receive \
  -H "Authorization: Bearer ir_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rcv-FXP-448120-1" \
  -d '{
    "reference": "UPS 1Z999AA10123456784",
    "received_on": "2026-09-30",
    "lines": [ { "purchase_order_line_id": 511, "qty": 24 } ]
  }'

Each line is { purchase_order_line_id, qty, unit_cost_cents? }. qty must be between 1 and what's outstanding on that line (ordered minus received), else 400 VALIDATION_ERROR. unit_cost_cents overrides the purchase order's cost for this receipt.

Everything outstanding:

curl -X POST https://app.ironregister.com/api/v1/purchase-orders/77/receive \
  -H "Authorization: Bearer ir_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rcv-FXP-448120-2" \
  -d '{ "all_outstanding": true }'

Returns 201:

{
  "status": "ok",
  "data": {
    "object": "receiving",
    "id": 41,
    "number": "R93-00041",
    "status": "posted",
    "lines": [
      { "purchase_order_line_id": 511, "product_id": 2062, "qty": 16, "unit_cost_cents": 1100 }
    ],
    "skipped": [
      { "purchase_order_line_id": 512, "sku": "GLK-G19G5", "qty": 2, "reason": "serialized" }
    ],
    "purchase_order": { "object": "purchase_order", "id": 77, "status": "partial", "…": "…" }
  }
}

Serialized items and firearms are received in Iron Register

Receiving a serialized unit records its serial number, and for a firearm that is the acquisition entry in the bound book. That is done in Iron Register (Receiving), where staff enter each serial. Through the API:

  • Naming a serialized line in lines is 409 SERIALIZED (details.purchase_order_line_id), and nothing is received.
  • With all_outstanding, serialized lines are left out and listed in skipped; everything else is received.
  • If only serialized lines are outstanding, the response is 409 NOTHING_OUTSTANDING with details.skipped.

Receive errors

Status Code When
409 INVALID_STATE The purchase order is a draft (submit it first), or received, closed or cancelled
409 SERIALIZED A named line is a serialized product
409 NOTHING_OUTSTANDING Nothing (non-serialized) is left to receive
400 VALIDATION_ERROR Unknown purchase_order_line_id, or qty outside 1…outstanding

Received serial numbers are never included in any API response or webhook; they're acquisition records.