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,releasedclass_session_idsource: e.g.bc_widget,hosted,walk_up,corporate,pos,apifromandto, on start timelocation_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: abookedreservation becomeschecked_in(the party is here). Firesrange.reservation.checked_in.POST /v1/range/reservations/{id}/no-show: abookedreservation becomesno_show, and its lane is flagged for reset. Firesrange.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,bookedandchecked_inreservations can be cancelled. Anything else returns409 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: truewhen 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.