API docs

Range

Permission: range (read / write). Only for Iron Register shops with the Range add-on; otherwise every endpoint returns 402 RANGE_ADDON_REQUIRED. Everything is filtered to the locations the key can see. Waivers and incident reports aren't in the API. The rental-gun fleet is readable (see Rental fleet); renting and returning guns happens at the register. Closing out a visit (overtime, rental returns) happens at the register too: you'll see it as a range.reservation.completed event, any overtime or fees as a sale.completed order, and each gun returned as range.rental.checked_in.

With write access you can book lanes and class seats through the same engine as the shop's booking widget and front desk: availability is re-checked the moment you book, the range's rules and pricing apply, and the guest gets the shop's confirmation email and waiver links. Then you can check parties in, mark no-shows and cancel.

Resources (lanes, bays, instructors)

GET /v1/range/resources. Filters: type (e.g. lane, instructor), location_id, updated_since, limit, cursor.

{ "object": "range_resource", "id": 14, "location_id": 93, "type": "lane", "booking_mode": "exclusive", "name": "Lane 3", "capacity": 2, "base_price_cents": 2500, "floor_status": "idle", "status": "active", "sort_order": 3, "updated_at": "2026-09-10T12:00:00Z" }

booking_mode is exclusive (one party per lane) or session_capacity (seats in a scheduled session).

Classes

GET /v1/range/classes

{ "object": "range_class", "id": 4, "location_id": 93, "product_id": 2142, "name": "Intro to Pistol", "instructor_name": "K. Alvarez", "duration_minutes": 180, "default_capacity": 12, "min_age": 18, "prerequisites": null, "what_to_bring": "Eye and ear protection", "updated_at": "2026-09-12T12:00:00Z" }

Class sessions

GET /v1/range/sessions?from=&to=&class_id=. from and to are ISO-8601 and filter on start time. They default to now through 30 days from now.

{ "object": "range_class_session", "id": 88, "class_id": 4, "location_id": 93, "resource_id": 21, "starts_at": "2026-10-03T14:00:00Z", "ends_at": "2026-10-03T17:00:00Z", "capacity": 12, "seats_booked": 7, "seats_left": 5, "price_cents": 8900, "status": "scheduled", "updated_at": "2026-09-24T12:00:00Z" }

Reservations

GET /v1/range/reservations. Filters:

  • status: held, booked, checked_in, in_progress, completed, cancelled, no_show, released
  • class_session_id
  • source: e.g. bc_widget, hosted, walk_up, corporate, pos, api
  • from and to, on start time
  • location_id, updated_since, limit, cursor

GET /v1/range/reservations/{id}

{
  "object": "range_reservation",
  "id": 3120,
  "location_id": 93,
  "class_session_id": null,
  "status": "booked",
  "source": "bc_widget",
  "party_size": 2,
  "starts_at": "2026-09-26T18:00:00Z",
  "ends_at": "2026-09-26T19:00:00Z",
  "contact": { "name": "Jordan Reyes", "email": "jordan.reyes@example.com", "phone": "330-555-0199" },
  "customer_ids": [1166],
  "payment_status": "paid",
  "paid_cents": 5000,
  "refunded_cents": 0,
  "order_id": null,
  "bigcommerce_order_id": "10442",
  "lines": [ { "kind": "resource", "resource_id": 14, "product_id": null, "description": "Lane 3 · 1 hour", "quantity": 1, "unit_cents": 5000, "total_cents": 5000 } ],
  "confirmed_at": "2026-09-24T21:02:00Z",
  "checked_in_at": null,
  "completed_at": null,
  "cancelled_at": null,
  "cancel_reason": null,
  "no_show_at": null,
  "created_at": "2026-09-24T21:00:00Z",
  "updated_at": "2026-09-24T21:02:00Z"
}

lines[].kind is resource, lane_time, rental or adjustment.

Availability

GET /v1/range/availability (read). The resources free for the whole window, priced for the party.

Parameter Notes
starts_at, ends_at Required, ISO-8601 (e.g. 2026-09-28T18:00:00Z)
location_id Optional; defaults to the key's only location, or the shop's main one
type Resource type, default lane
party_size Default 1; affects pricing and the range's rules
lane_type Optional lane type, e.g. rifle, or a comma list
curl "https://app.ironregister.com/api/v1/range/availability?starts_at=2026-09-28T18:00:00Z&ends_at=2026-09-28T19:00:00Z&party_size=2" \
  -H "Authorization: Bearer $KEY"
{
  "status": "ok",
  "data": [
    { "object": "range_availability", "resource": { "object": "range_resource", "id": 14, "location_id": 93, "type": "lane", "booking_mode": "exclusive", "name": "Lane 3", "capacity": 2, "base_price_cents": 2500, "floor_status": "idle", "status": "active", "sort_order": 3, "updated_at": "2026-09-10T12:00:00Z" }, "price_cents": 2500, "starts_at": "2026-09-28T18:00:00Z", "ends_at": "2026-09-28T19:00:00Z", "party_size": 2 }
  ],
  "meta": { "count": 1 }
}

A missing or malformed time is 400 VALIDATION_ERROR. Availability is a snapshot; the booking call checks again.

Book a lane or a class seat

POST /v1/range/reservations (write). Send an Idempotency-Key, so a retry can't book twice.

A lane for a time window: resource_ids (from availability), starts_at, ends_at, party_size, and optionally location_id.

curl -X POST https://app.ironregister.com/api/v1/range/reservations \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: booking-7731" \
  -d '{
    "resource_ids": [14],
    "starts_at": "2026-09-28T18:00:00Z",
    "ends_at": "2026-09-28T19:00:00Z",
    "party_size": 2,
    "customer": { "email": "jordan@example.com", "first_name": "Jordan", "last_name": "Reyes", "phone": "330-555-0199" },
    "paid": true,
    "amount_cents": 5000,
    "external_reference": "BOOK-7731"
  }'

Seats in a class session: class_session_id and party_size. The session's own time and price apply.

curl -X POST https://app.ironregister.com/api/v1/range/reservations \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: class-88-jordan" \
  -d '{
    "class_session_id": 88,
    "party_size": 1,
    "paid": false,
    "contact": { "name": "Jordan Reyes", "email": "jordan@example.com", "phone": "330-555-0199" }
  }'
Field Notes
customer_id An existing customer, or send customer (matched by email, otherwise created; a new one needs first_name and last_name). Optional.
contact {name, email, phone} for the booking; defaults from the customer
paid Default true: paid on your channel. The reservation is confirmed with payment status paid and paid_cents = amount_cents (default: the booking's price). false books it unpaid, and the shop settles it at the desk.
amount_cents What you collected, when paid
external_reference Your booking id; recorded in the shop's audit log
notes Lane bookings only

Returns 201 with the reservation (status: "booked", source: "api"). The guest gets the shop's confirmation email with waiver links, and range.reservation.booked fires.

Error When
409 RESOURCE_UNAVAILABLE The lane was taken for that time (or is out of service)
409 SESSION_FULL Not enough seats left in the class session
422 INVALID_RESOURCE A resource that isn't active, or a class that's no longer offered
422 INVALID_SESSION The class session isn't open for booking
422 CONSTRAINT_VIOLATION The booking breaks one of the range's rules (party size, lane type, age and so on)
404 NOT_FOUND No such class session, or a location the key can't see
400 VALIDATION_ERROR Missing resource_ids (or class_session_id), a bad time window, or an unknown customer_id

Check in and no-show

  • POST /v1/range/reservations/{id}/check-in: a booked reservation becomes checked_in (the party is here). Fires range.reservation.checked_in.
  • POST /v1/range/reservations/{id}/no-show: a booked reservation becomes no_show, and its lane is flagged for reset. Fires range.reservation.no_show.

Anything other than booked is 409 INVALID_STATE.

Cancel a reservation

POST /v1/range/reservations/{id}/cancel with an optional { "reason": "…" } (write).

  • Only held, booked and checked_in reservations can be cancelled. Anything else returns 409 INVALID_STATE.
  • The seats or lane time are released, and the guest gets the shop's cancellation email if the booking was confirmed.
  • Refunds aren't issued by the API. The response has requires_manual_refund: true when money was paid and not yet refunded; the shop refunds it in Iron Register or BigCommerce.

Rental fleet

Read-only, with range:read. The guns the range rents out, where each one is, and the custody history. Guns are rented and returned at the register (or on Range → Rentals); the API sees the result. Serial numbers are readable, never writable. Everything is filtered to the locations the key can see.

List the fleet

GET /v1/range/rentals. Filters: status (available, checked_out, maintenance = out of service, retired), location_id, updated_since, limit, cursor. Any other status is 400 VALIDATION_ERROR.

curl "https://app.ironregister.com/api/v1/range/rentals?status=checked_out" \
  -H "Authorization: Bearer ir_live_…"
{
  "object": "range_rental",
  "id": 43,
  "location_id": 93,
  "product_id": 2053,
  "serialized_item_id": 2190,
  "name": "Glock Inc. 19 Gen5",
  "caliber": "9mm",
  "firearm_type": "pistol",
  "serial_number": "BZRW41177C",
  "tag": "IRF0000003",
  "status": "checked_out",
  "fee_cents": 1500,
  "min_age": 21,
  "round_count": 2150,
  "last_serviced_on": "2026-09-01",
  "out_of_service_reason": null,
  "checkout": {
    "object": "range_rental_checkout",
    "id": 39,
    "rental_id": 43,
    "location_id": 93,
    "customer_id": 1137,
    "reservation_id": 443,
    "order_id": 5120,
    "checked_out_at": "2026-09-25T14:29:15Z",
    "condition_out": null,
    "checked_in_at": null,
    "condition_in": null,
    "rounds_fired": null
  },
  "updated_at": "2026-09-25T14:29:15Z"
}
Field Meaning
name, caliber, firearm_type From the catalog product
tag The rental tag barcode staff scan at the counter
status available, checked_out, maintenance (out of service) or retired
fee_cents The gun's rental price
min_age The gun's minimum age (its own, else the product's)
round_count Lifetime rounds, from the rounds fired recorded on each return
last_serviced_on Date, when recorded
out_of_service_reason Why it's out of service (e.g. Returned: Needs cleaning); null unless status is maintenance
checkout The open checkout while the gun is out, else null

One gun

GET /v1/range/rentals/{id}. The same object plus recent_checkouts: its last 20 checkouts, newest first.

Checkout history

GET /v1/range/rental-checkouts. Every time a gun went out, oldest first. Filters: rental_id, customer_id, reservation_id, open (true = still out, false = returned), location_id, updated_since, limit, cursor.

{
  "object": "range_rental_checkout",
  "id": 33,
  "rental_id": 43,
  "location_id": 93,
  "customer_id": 1133,
  "reservation_id": null,
  "order_id": null,
  "checked_out_at": "2026-09-25T10:15:49Z",
  "condition_out": null,
  "checked_in_at": "2026-09-25T10:16:19Z",
  "condition_in": "Needs cleaning: heavy carbon",
  "rounds_fired": 150
}
Field Meaning
customer_id Who had the gun (read the customer with customers:read)
reservation_id The lane visit it went out on, when there was one
order_id The sale that rang up the rental fee
condition_out / condition_in As recorded by staff: Good, Needs cleaning, Damaged, with an optional note
rounds_fired Recorded on return, when the range tracks it

Webhooks

range.rental.checked_out, range.rental.checked_in, range.rental.out_of_service and range.rental.returned_to_service. Each carries the range_rental object as it is after the change. range.rental.checked_in also carries last_checkout: the checkout that just closed, with its condition and rounds fired. A gun returned as Needs cleaning or Damaged sends checked_in followed by out_of_service.