API docs

Orders

Permission: orders (read / write).

An order is a sale on the POS. That covers sales rung up at a register, online orders brought to the counter, and orders created through this API.

The order object

{
  "object": "order",
  "id": 5521,
  "number": "S93-000037",
  "status": "completed",
  "source": "api",
  "fulfillment": "ship",
  "location_id": 93,
  "register_id": null,
  "customer": { "object": "customer", "id": 1166, "first_name": "Jordan", "last_name": "Reyes", "email": "jordan.reyes@example.com", "...": "…" },
  "external_reference": "GB-1048812233",
  "contains_firearms": false,
  "subtotal_cents": 3198,
  "discount_cents": 0,
  "coupon_code": null,
  "trade_in_allowance_cents": 0,
  "tax_cents": 0,
  "shipping_cents": 995,
  "total_cents": 4193,
  "paid_cents": 4193,
  "refunded_cents": 0,
  "tax_rate_bps": 725,
  "shipping_address": { "name": "Jordan Reyes", "line1": "3400 Kent Rd", "city": "Stow", "state": "OH", "postal_code": "44224", "country": "US" },
  "lines": [
    { "object": "order_line", "id": 9801, "line_type": "product", "product_id": 2062, "sku": "AMM-9-115-50", "description": "Blazer Brass 9mm 115gr FMJ (50)", "qty": 2, "unit_price_cents": 1599, "discount_cents": 0, "tax_cents": 0, "line_total_cents": 3198, "taxable": true, "is_firearm": false, "serialized_item_id": null, "serial_number": null, "refunded_qty": 0 }
  ],
  "payments": [
    { "object": "payment", "id": 7710, "tender": "external", "kind": "charge", "amount_cents": 4193, "status": "captured", "card_brand": null, "card_last4": null, "reference": "GB-PAY-1", "entry": null, "processor": null, "approval_code": null, "refund_of_payment_id": null, "created_at": "2026-09-25T01:49:10Z" }
  ],
  "bigcommerce_order_id": null,
  "completed_at": "2026-09-25T01:49:10Z",
  "voided_at": null,
  "created_at": "2026-09-25T01:49:10Z",
  "updated_at": "2026-09-25T01:49:10Z"
}
Field Values / notes
status open, held (parked, to be finished at the register), awaiting_transfer (paid; the firearm is waiting on its transfer paperwork), completed, voided, refunded, partially_refunded
source pos (register), api, and other channels
fulfillment in_store, ship, pickup
customer The full customer when the key has customers:read (plus date_of_birth with customer_dob:read). Otherwise just { "object": "customer", "id": … }. null for walk-in sales.
external_reference Your reference, on orders created through the API. null on every other order.
lines[].line_type product, fee, service, transfer_fee, custom, gift_certificate, range, membership (a sign-up, initiation fee or membership dues)
lines[].serial_number The unit sold, when the product is serialized
payments[].tender cash, card, check, gift_card, store_credit, external, layaway, on_account
payments[].kind charge or refund
payments[].reference Only for external and check tenders
payments[].entry For card payments when the shop has card processing on: terminal (charged on the shop's card terminal), token (a saved card, e.g. membership dues), manual (typed in from a separate terminal, approved by a manager). null otherwise.
payments[].processor payroc when the card went through the shop's processor (simulator in a test shop). null for cards recorded by hand and every other tender.
payments[].approval_code The processor's approval code on a processor card charge. null otherwise.
payments[].refund_of_payment_id On a refund that went back to the card: the id of the charge it returned money to.

List

GET /v1/orders, filtered to the locations the key can see.

Filter
status Any status above
source e.g. api
customer_id
external_reference Your reference
number e.g. S93-000037
location_id, updated_since, limit, cursor

Get

GET /v1/orders/{id}. An order at a location the key can't see is a 404.

Create an order

POST /v1/orders (write) puts a sale from an outside channel onto the POS, so the stock comes off the shelf Iron Register owns.

Field
lines Required, 1–100 lines: { "product_id" or "sku", "qty": 1–9999, "unit_price_cents"?, "serial_number"? }. Without unit_price_cents the POS price applies (including sale and customer-group prices).
location_id Defaults to the key's only location, else the shop's default
external_reference Up to 36 characters. Makes the create idempotent: sending an order with a reference that already exists returns the existing order with 200 and creates nothing. A reference already taken by something other than an API order is 409 EXTERNAL_REFERENCE_TAKEN.
channel Up to 40 characters, e.g. GunBroker. Shown to staff and used as the payment processor label.
customer_id or customer An existing customer, or { email, first_name, last_name, phone?, company?, address{}?, marketing_opt_in? }. With customer, the buyer is matched by email, or created if new.
fulfillment ship, pickup or in_store. Default: ship when shipping_address is sent, otherwise pickup.
shipping_address { name, company, line1, line2, city, state, postal_code, country, phone, email, ffl_number }
shipping_cents Shipping charged
tax_exempt true when the channel collected and remits the sales tax (a marketplace facilitator), so Iron Register doesn't charge it again. Otherwise tax is computed at the location's rate.
paid Default true: paid on the outside channel
payment_reference The channel's payment or transaction id
notes Up to 1,000 characters, shown to staff

What happens next:

  • Paid, no serialized or firearm lines. The order completes immediately. An external payment for the full total is recorded and stock comes off the location. The response has status: "completed" and next_action: null.
  • Any serialized or firearm line. The order is saved held, with next_action: "complete_at_register". Staff open it at the register, pick the serial number, and for a firearm run the Form 4473 or the FFL-to-FFL shipment. When they complete it, sale.completed fires. The API never completes a firearm disposition. A serial_number on a line is passed to staff as a request; it doesn't reserve the unit. Firearm lines with qty over 1 are split into one line per unit.
  • paid: false. The order is held with next_action: "collect_payment_at_register".
curl -X POST https://app.ironregister.com/api/v1/orders \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "external_reference": "GB-1048812233",
    "channel": "GunBroker",
    "lines": [ { "sku": "AMM-9-115-50", "qty": 2, "unit_price_cents": 1599 } ],
    "customer": { "email": "jordan.reyes@example.com", "first_name": "Jordan", "last_name": "Reyes" },
    "shipping_cents": 995,
    "shipping_address": { "name": "Jordan Reyes", "line1": "3400 Kent Rd", "city": "Stow", "state": "OH", "postal_code": "44224", "country": "US" },
    "tax_exempt": true,
    "payment_reference": "GB-PAY-771"
  }'

Returns 201 with the order plus next_action.

Possible errors:

  • 400 VALIDATION_ERROR: an unknown product (lines.N.product), a bad qty or price, or an unknown customer_id.
  • 409 INSUFFICIENT_STOCK: the shop doesn't allow negative stock and the location is short. Nothing is saved.

See the marketplace orders guide for a full walkthrough.

Void an order

POST /v1/orders/{id}/void with { "reason": "Customer cancelled" } (write).

  • Voiding restores stock and records a refund of the payments.
  • Orders with firearm lines are refused with 409 FIREARM_ORDER, because they're voided in Iron Register, where the 4473 is cancelled with them.
  • Also refused: ALREADY_VOIDED, and SALE_REFUNDED once any part has been refunded.

Returns the voided order.