Download OpenAPI specification:
Routespring Crew Travel API lets airlines automate hotel and flight bookings for their crew — directly from their crew management system, without manual intervention.
Submit your roster. Routespring compares it against existing bookings, determines what actions are required, and books hotels for layovers and positioning flights for deadhead legs — automatically, based on the rules and preferences your airline has configured.
Schedule-driven IROP / ad-hoc
POST /crew/schedules POST /crew/action-items
(diff against prior POST /crew/bookings/request
submission)
│ │
└───────────────┬───────────────┘
▼
Booking engine
(config · policy)
│
┌────────┴────────┐
│ │
Hotels Deadhead flights
│ │
▼ ▼
Auto-booked Auto-booked
──────────────────────────────
or NEEDS_REVIEW if price ceiling
exceeded or policy blocks
│ │
└────────┬────────┘
▼
Action Items → Bookings
Processing is asynchronous. Every POST /crew/schedules returns a
schedule_id immediately — poll /processing-status to track progress,
then retrieve action items and bookings once processing completes.
Parts of the automation are controlled by settings that are not exposed on
this API. Each one is silent when unset — the lifecycle still runs and
returns 200/202, the work just lands as a review item instead of a
booking. If automation looks inert, check these first.
Get them provisioned before your first POST /crew/schedules. Switching a
hotel contract on for automated booking is a reviewed action, so it cannot be
arranged in the moment: submit a schedule against a station whose contract is
not yet approved and the layover comes back NEEDS_REVIEW — configuring the
hotel is not on its own enough to make it bookable.
| Prerequisite | What you see while it is unset | Where it is set |
|---|---|---|
| Schedules API access | registering an OAuth client does not offer the schedules:read / schedules:write scopes |
ask Routespring |
| Crew booking account | automated bookings have no account to book under | you — PUT /crew/config/company (crew_booker_email) |
| Hotel contract + contracted rates | a layover returns NEEDS_REVIEW with No hotel configuration found for station: XXX, even though GET /crew/config/hotels shows the hotel |
Hotel Manager UI, or these API routes |
| Deadhead auto-booking | every NEW_DEADHEAD comes back PENDING_REVIEW / BOOK_MANUALLY with a flight_options_url — pick an option and commit it with POST /crew/bookings/request |
ask Routespring |
| Booking modification | a date or station change is surfaced as a review item instead of being amended with the supplier | ask Routespring |
| Crew rotation / swap execution | an A↔B swap is surfaced as a review item instead of being executed | ask Routespring |
| Cancellation execution | a dropped layover is surfaced as a review item instead of being cancelled (the window itself is yours: cancellation_lookback_days) |
ask Routespring |
| Rate-match strictness | a supplier price that differs from the contracted rate fails the booking rather than flagging it for review | ask Routespring |
PUT /crew/config/company accepts only crew_booker_email and
cancellation_lookback_days. The switches above authorise automated spend,
so they stay operator-controlled — there is no API call that turns them on.
▶ Launch the crew lifecycle walkthrough ↗
A guided, click-through demo of the full crew lifecycle against the sandbox.
Paste your sandbox client_id / client_secret (from the dashboard) and a
traveler email, then step through it live: get a token → configure → submit a
schedule → poll → action items → bookings → select and confirm a deadhead flight →
full trip details. This reference page (everything below) is the contract; the
demo is the same flow, executed for you one step at a time.
A copy-paste guide for integrators. Walks the lifecycle in order: one-time configuration → schedule-driven booking → IROP / airline-injected booking → ops & retries → error responses you should handle.
Every block is a real curl invocation. Bodies are inline so each one is self-contained. Variables ($BASE, $BEARER) are set once in §0.
The crew API authenticates with an OAuth2 client_credentials flow served by the Routespring auth service. You exchange a long‑lived client_id / client_secret for a short‑lived access token (JWT), and send that token as the Bearer on every crew API call. (The gateway validates the access token and resolves it to your company — there is no airline id in the URL.)
# Base of the Routespring OAuth service.
export AUTH_BASE="https://oauth-sandbox.routespring.com" # sandbox
# export AUTH_BASE="https://oauth.routespring.com" # production (host confirmed at onboarding)
Step 1 — get client credentials (one-time). Register an OAuth2 client, or receive client_id/client_secret directly during onboarding. Registration authenticates as your Routespring user (Bearer <email>:<sToken>):
curl -sS -X POST "$AUTH_BASE/oauth/register?name=crew-integration&scopes=events:read%20events:write" \
-H "Authorization: Bearer your-user@airline.com:YOUR_STOKEN"
# -> { "client_id": "0687e8bd-…", "client_secret": "vwWDwBbC5QJ…" }
Step 2 — exchange them for an access token. HTTP Basic (client_id:client_secret) + grant_type=client_credentials:
curl -sS -X POST "$AUTH_BASE/oauth/token?grant_type=client_credentials" \
-u "CLIENT_ID:CLIENT_SECRET"
# -> { "access_token": "<JWT>", "refresh_token": "<UUID>", "token_type": "Bearer",
# "expires_in": 899, "scope": "events:read events:write" }
Step 3 — refresh when it expires (~15 min). No credentials needed, just the refresh token:
curl -sS -X POST "$AUTH_BASE/oauth/refresh?refresh_token=YOUR_REFRESH_TOKEN"
# -> a fresh access_token (+ expires_in)
Now set the variables the rest of this guide uses — $BEARER is the access_token from Step 2:
export BASE="https://sandbox.routespring.com/api/v1" # sandbox: gateway host + /api/v1
# export BASE="https://api.routespring.com/api/v1" # production (host confirmed at onboarding)
export BEARER="<access_token from /oauth/token>" # short-lived; refresh via Step 3
Why a Bearer token? Every endpoint under
/crew/…requiresAuthorization: Bearer $BEARER. Your airline (tenant) is derived from the token itself — there is no airline id in the URL. The hotel directory at/hotelsalso requires it. Missing, malformed, or expired bearer →401 UNAUTHORIZED(refresh and retry).
These aren't obvious from the endpoint reference, but each one will cost you a debugging session if you don't know it up front:
Everything off POST /schedules is asynchronous. The submit returns 202 with processing_status: QUEUED. Poll …/processing-status until it is terminal (COMPLETED / PARTIAL / FAILED) before trusting …/action-items. An empty action_items array immediately after submit usually just means processing hasn't finished — not that nothing was derived.
Deadhead diffing is stateful. Deadhead (FLIGHT) action items are derived by diffing your submission against the previous submission's persisted legs, keyed on employee_id + pairing_number + duty_date + leg_sequence. Consequences:
DEADHEAD_CANCELLED for the old leg and a NEW_DEADHEAD for the new one.employee_id to get a clean NEW_DEADHEAD with no cancellation noise.Keep dates consistent within a duty period. A leg's duty_date must agree with the date of its dep_time_local / expected_dep_time_local. If they disagree, the leg is dropped during grouping and nothing derives (you'll see an empty action_items with no error).
A deadhead books the positioning flight you submit. A DH leg books a flight on its explicit from_airport → to_airport, exactly as you submit them — the route is taken as-is, not derived from the crew's home base.
Deadheads and hotels both book automatically. /schedules derives the need and books it — a hotel layover, or a NEW_DEADHEAD positioning flight on the DH leg's explicit from_airport → to_airport, is auto-booked through the supplier path. Auto-booking is gated by policy: a deviation (e.g. an early check-in, or a route/date the supplier can't fulfill) or a strict price mismatch surfaces the item as NEEDS_REVIEW for an agent to resolve instead.
Flight booking requires a date of birth. The crew member's traveler profile must have a DOB (airline/GDS requirement). Without one, a flight POST /bookings/request returns 422.
Flight options are short-lived and opaque. flight_option_id values from GET /flight-options expire (~20 min) and must be passed back verbatim to POST /bookings/request — never parse or construct them. Search fresh and commit promptly; an expired id fails the commit.
Read a booking in two steps. GET /bookings/{id}/status refreshes the live supplier state (PNR / ticket / transaction / trip-session ids) and persists it; then GET /bookings/{id} returns the full record with those ids on the flight block (pnr, ticket_id, transaction_id, trip_session_id).
Raw supplier itinerary (optional deep dive). Beyond the crew booking record, you can fetch the full supplier itinerary — complete PNR, fare breakdown, segments — from the flight API at GET $BASE/bookings/trips/{trip_session_id} (see the Flights tag), using the trip_session_id returned by GET /crew/bookings/{id}.
Run these once when onboarding a new airline. They are idempotent — re-running a PUT replaces the config (creating a new version under the hood).
crew_booker_email is the Routespring account under which engine-driven crew bookings — hotel layovers the diff engine books and later amends/cancels — are placed; set it first, before any roster flows. (A manual flight commit via POST /crew/bookings/request is booked under the user whose bearer token makes that call, not this account — so for flights the actor is the API caller.) The other setting exposed here is cancellation_lookback_days; omit a field on PUT to leave it unchanged. The automation execution levers (cancellation / modification / swap / deadhead-flight execution, strict price matching, NOC stats) are Routespring-managed and not part of this API. Deadhead positioning flights are booked automatically by the engine, the same as hotel layovers.
curl -sS -X PUT "$BASE/crew/config/company" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"crew_booker_email": "crew-ops@flymx.com"
}'
# Read the current config back any time:
curl -sS "$BASE/crew/config/company" -H "Authorization: Bearer $BEARER"
Tell Routespring which preferred hotel to book at each crew base (one hotel per base). The booking engine prefers it over open-market searches.
curl -sS -X PUT "$BASE/crew/config/hotels" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"bases": [
{
"base_station": "SLC",
"hotels": [
{
"property_id": "hilton-slc-airport",
"chain": "HILTON",
"policy": "STANDARD",
"standard_check_in_time": "15:00",
"standard_check_out_time": "11:00",
"buffer_minutes": 60,
"contract_rate": { "amount": 149.00, "currency": "USD" },
"rate_periods": [
{ "effective_from": "2026-01-01", "effective_to": "2026-06-30", "rate": { "amount": 149.00, "currency": "USD" } },
{ "effective_from": "2026-07-01", "effective_to": "2026-12-31", "rate": { "amount": 169.00, "currency": "USD" } }
]
}
]
},
{
"base_station": "JFK",
"hotels": [
{
"property_id": "TVP-JFK-9001",
"policy": "FLEXIBLE",
"flex_window_minutes": 120,
"buffer_minutes": 30
}
]
}
]
}' | jq .
GDS vs DIRECT.
GDShotels are looked up against the GDS global inventory byproperty_id; an unknown id returns422 UNKNOWN_HOTEL_PROPERTYat PUT time.DIRECThotels are managed by you and not validated against any external system.
The price ceiling for auto-booking a positioning flight:
curl -sS -X PUT "$BASE/crew/config/flight-rules" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"auto_book_max_price": { "amount": 800.00, "currency": "USD" }
}' | jq .
Preferred / blacklisted carriers and cabin:
curl -sS -X PUT "$BASE/crew/config/flights" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"preferred_airlines": ["DL", "UA", "AA"],
"blacklisted_airlines": ["F9"],
"preferred_cabin": "ECONOMY",
"same_carrier_preference": true
}' | jq .
curl -sS "$BASE/crew/config/hotels" -H "Authorization: Bearer $BEARER" | jq '.bases | length'
curl -sS "$BASE/crew/config/flight-rules" -H "Authorization: Bearer $BEARER" | jq .
This is the standard loop: you POST a roster and Routespring diffs it, derives action items, and books against your config. Hotel layovers are diffed against existing booking actuals; deadhead positioning against the prior submission's persisted legs.
SUBMIT_RESPONSE=$(curl -sS -X POST "$BASE/crew/schedules" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "MX-20260527-4821",
"as_of": "2026-05-27T08:00:00Z",
"schedule_start_date": "2026-05-16",
"schedule_end_date": "2026-05-31",
"pairings": [
{
"pairing_number": "MX-20260527-4821",
"origin_base": "SLC",
"crew": [
{ "employee_id": "EMP-1042", "name": "Jordan Reyes", "rank": "CA", "home_base": "SLC", "traveller_email": "jreyes@flymx.com" },
{ "employee_id": "EMP-2317", "name": "Alex Pham", "rank": "FO", "home_base": "SLC", "traveller_email": "apham@flymx.com" }
],
"duty_periods": [
{
"duty_period_num": 1,
"duty_date": "2026-05-27",
"fdp_start_local": "2026-05-26T23:30:00-06:00",
"fdp_end_local": "2026-05-27T12:15:00-04:00",
"legs": [
{ "leg_sequence": 1, "activity_code": "FLT", "flight_number": "MX401", "from_airport": "SLC", "to_airport": "LAX", "dep_time_local": "2026-05-27T01:00:00-06:00", "arr_time_local": "2026-05-27T01:30:00-07:00", "aircraft_type": "A220-300", "aircraft_tail": "N412MX" },
{ "leg_sequence": 2, "activity_code": "FLT", "flight_number": "MX588", "from_airport": "LAX", "to_airport": "IAD", "dep_time_local": "2026-05-27T03:00:00-07:00", "arr_time_local": "2026-05-27T14:15:00-04:00", "aircraft_type": "A220-300", "aircraft_tail": "N412MX" }
],
"layover": { "station": "IAD", "booking_required": true, "checkin_time_local": "2026-05-27T14:45:00-04:00", "checkout_time_local": "2026-05-28T01:00:00-04:00" }
},
{
"duty_period_num": 2,
"duty_date": "2026-05-28",
"fdp_start_local": "2026-05-28T02:00:00-04:00",
"fdp_end_local": "2026-05-28T09:30:00-05:00",
"legs": [
{ "leg_sequence": 3, "activity_code": "FLT", "flight_number": "MX210", "from_airport": "IAD", "to_airport": "TUL", "dep_time_local": "2026-05-28T04:00:00-04:00", "arr_time_local": "2026-05-28T05:45:00-05:00", "aircraft_type": "A220-300", "aircraft_tail": "N388MX" },
{ "leg_sequence": 4, "activity_code": "DH", "from_airport": "TUL", "to_airport": "DAL", "expected_dep_time_local": "2026-05-28T07:00:00-05:00", "expected_arr_time_local": "2026-05-28T08:15:00-05:00", "booking_required": true }
],
"layover": { "station": "DAL", "booking_required": true, "checkin_time_local": "2026-05-28T08:45:00-05:00", "checkout_time_local": "2026-05-29T00:30:00-05:00" }
},
{
"duty_period_num": 3,
"duty_date": "2026-05-29",
"fdp_start_local": "2026-05-29T01:30:00-05:00",
"fdp_end_local": "2026-05-29T04:00:00-06:00",
"legs": [
{ "leg_sequence": 5, "activity_code": "FLT", "flight_number": "MX305", "from_airport": "DAL", "to_airport": "SLC", "dep_time_local": "2026-05-29T03:00:00-05:00", "arr_time_local": "2026-05-29T03:45:00-06:00", "aircraft_type": "A220-300", "aircraft_tail": "N401MX" }
]
}
]
}
]
}')
echo "$SUBMIT_RESPONSE" | jq .
# Capture the minted schedule_id for the next steps.
export SCHEDULE_ID=$(echo "$SUBMIT_RESPONSE" | jq -r .schedule_id)
echo "SCHEDULE_ID=$SCHEDULE_ID"
Expected: 202 Accepted with processing_status: QUEUED and a generated schedule_id like UPL-35-1779633752601.
Deadhead in v1. The JSON
POST /schedulespath derives both HOTEL and FLIGHT (deadhead) action items from the same submission. JSON is the only ingestion format — there is no file-upload endpoint.
curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/processing-status" \
-H "Authorization: Bearer $BEARER" | jq .
status walks: QUEUED → PROCESSING → BOOKING → COMPLETED (or FAILED).
Why status can revert from COMPLETED to BOOKING. The hotel branch can flip the upload to COMPLETED before the deadhead (flight) branch finishes its supplier calls. If any FLIGHT task is still non-terminal, this endpoint reports
BOOKINGrather thanCOMPLETEDso you don't stop polling early. Once every flight task isBOOKED/FAILED/CANCELLED, status reportsCOMPLETED.
# All action items for the submission
curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/action-items" \
-H "Authorization: Bearer $BEARER" | jq '.action_items[] | {action_id, type, classification, priority, disposition}'
# Just FLIGHT (deadhead) items
curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/action-items?type=FLIGHT" \
-H "Authorization: Bearer $BEARER" | jq .
# Just items needing human review
curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/action-items?disposition=PENDING&priority=P1" \
-H "Authorization: Bearer $BEARER" | jq .
Action-item classifications you'll see: NEW_LAYOVER, LAYOVER_DATE_CHANGE, LAYOVER_CITY_CHANGE, LAYOVER_CANCELLED, NEW_DEADHEAD, DEADHEAD_TIME_CHANGE, DEADHEAD_CANCELLED, and the data-quality / IROP groups documented in openapi.yaml.
# By employee
curl -sS "$BASE/crew/bookings?employee_id=EMP-12047" \
-H "Authorization: Bearer $BEARER" | jq '.bookings[] | {booking_id, type, status, hotel, flight}'
# By pairing
curl -sS "$BASE/crew/bookings?pairing_number=P-23845" \
-H "Authorization: Bearer $BEARER" | jq .
# By IROP reference
curl -sS "$BASE/crew/bookings?irop_ref=IROP-2026-06-15-EWR-001" \
-H "Authorization: Bearer $BEARER" | jq .
# Only flight bookings in a date range
curl -sS "$BASE/crew/bookings?type=FLIGHT&from_date=2026-06-01&to_date=2026-06-30" \
-H "Authorization: Bearer $BEARER" | jq .
Filter is required.
GET /bookingswith no filter returns400 INVALID_REQUEST. Pass any anchor key:employee_id,pairing_number,flight_number,irop_ref,schedule_id. The booking index is denormalised so any one anchor is a single-indexed lookup.
export BOOKING_ID="BKG-..." # from §2.4
curl -sS "$BASE/crew/bookings/$BOOKING_ID" \
-H "Authorization: Bearer $BEARER" | jq .
curl -sS "$BASE/crew/bookings/$BOOKING_ID/status" \
-H "Authorization: Bearer $BEARER" | jq .
# returns current_status + the full transition history
# In-place hotel date-window change (preserves booking_id + supplier confirmation)
curl -sS -X PATCH "$BASE/crew/bookings/$BOOKING_ID" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"reason": "PAIRING_EXTENDED",
"hotel": { "check_out_date": "2026-06-16" }
}' | jq .
Side effect:
manual_overridebecomestrue. After a successful PATCH, the diff engine will not modify this booking on subsequent schedule submissions. Release it back to auto-management by PATCHing{ "manual_override": false }(alone or alongside other changes).
curl -sS -X DELETE "$BASE/crew/bookings/$BOOKING_ID?reason=roster_change" \
-H "Authorization: Bearer $BEARER" | jq .
A cancelled booking whose pairing later reappears in a schedule will not auto-recreate — re-issue explicitly with POST /bookings/request.
When ops already knows what they need and the roster system hasn't caught up.
Use POST /action-items when the airline has identified the requirement but wants Routespring to auto-select against the active hotel config.
curl -sS -X POST "$BASE/crew/action-items" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"type": "HOTEL",
"classification": "IROP_HOTEL_EXTEND",
"priority": "P1",
"source": "IROP",
"irop_ref": "IROP-2026-06-15-EWR-001",
"anchors": {
"traveller_email": "jordan.reyes@flymx.com",
"employee_id": "EMP-12047",
"pairing_number": "P-23845"
},
"hotel": {
"city": "EWR",
"check_in_date": "2026-06-15",
"check_out_date": "2026-06-16",
"num_nights": 1
},
"notes": "Crew weather-diverted to EWR; extend layover."
}' | jq .
curl -sS -X POST "$BASE/crew/bookings/request" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"type": "HOTEL",
"classification": "IROP_HOTEL_EXTEND",
"source": "IROP",
"irop_ref": "IROP-2026-06-15-EWR-001",
"anchors": {
"traveller_email": "jordan.reyes@flymx.com",
"employee_id": "EMP-12047",
"pairing_number": "P-23845"
},
"hotel": {
"city": "EWR",
"property_id": "24355",
"check_in_date": "2026-06-15",
"check_out_date": "2026-06-16"
}
}' | jq .
Omit property_id to let Routespring auto-select per priority order.
curl -sS "$BASE/crew/flight-options?from_airport=EWR&to_airport=SLC&date=2026-06-15&arrive_by=2026-06-15T20:00:00Z" \
-H "Authorization: Bearer $BEARER" | jq '.options[] | {flight_option_id, airline_code, flight_number, dep_time, arr_time, price}'
Returns options each carrying a flight_option_id (a supplier offer id) — held for 20 minutes. To commit one, POST it back with the crew member's traveller_email on anchors.
curl -sS -X POST "$BASE/crew/bookings/request" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"type": "FLIGHT",
"classification": "IROP_NEW_DEADHEAD",
"source": "IROP",
"irop_ref": "IROP-2026-06-15-EWR-001",
"anchors": {
"traveller_email": "jordan.reyes@flymx.com",
"employee_id": "EMP-12047",
"pairing_number": "P-23845"
},
"flight": {
"flight_option_id": "REPLACE_ME_FROM_3.3"
},
"notes": "Manager-selected from /flight-options"
}' | jq .
Or commit a flight without a prior /flight-options search by passing the route + date:
curl -sS -X POST "$BASE/crew/bookings/request" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{
"type": "FLIGHT",
"classification": "IROP_NEW_DEADHEAD",
"source": "IROP",
"irop_ref": "IROP-2026-06-15-EWR-002",
"anchors": { "traveller_email": "jordan.reyes@flymx.com", "employee_id": "EMP-12047" },
"flight": {
"from_airport": "EWR",
"to_airport": "SLC",
"depart_date": "2026-06-15",
"depart_after": "2026-06-15T09:00:00Z",
"arrive_by": "2026-06-15T20:00:00Z",
"cabin_class": "ECONOMY"
}
}' | jq .
Use to look up property_ids before adding hotels to your config.
Any combination of name, city, airport_code (at least one required).
# By partial name
curl -sS "$BASE/hotels?name=Hilton%20Newark&limit=10" \
-H "Authorization: Bearer $BEARER" | jq '.hotels[] | {property_id, name, city}'
# By city
curl -sS "$BASE/hotels?city=Newark&limit=10" \
-H "Authorization: Bearer $BEARER" | jq '.hotels[] | {property_id, name, city}'
# By IATA airport code (3 letters) - matches hotels that name the airport
# AND hotels in the airport's city per Routespring's airport directory
curl -sS "$BASE/hotels?airport_code=EWR&limit=10" \
-H "Authorization: Bearer $BEARER" | jq '.hotels[] | {property_id, name, city}'
# Combine filters - results are unioned and deduplicated on property_id
curl -sS "$BASE/hotels?airport_code=EWR&name=Hilton&limit=10" \
-H "Authorization: Bearer $BEARER" | jq '.hotels[] | {property_id, name, city}'
Data-quality flags and duplicate-row detections from the diff engine:
curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/audit-events" \
-H "Authorization: Bearer $BEARER" | jq .
# Action items as CSV
curl -sS -o actions.csv "$BASE/crew/schedules/$SCHEDULE_ID/actions.csv" \
-H "Authorization: Bearer $BEARER"
# Single retry
curl -sS -X POST "$BASE/crew/bookings/$BOOKING_ID/retry" \
-H "Authorization: Bearer $BEARER" | jq .
# What is queued
curl -sS "$BASE/crew/bookings/retry-queue?limit=20" \
-H "Authorization: Bearer $BEARER" | jq .
# Recent bulk-retry jobs
curl -sS "$BASE/crew/bookings/retry-jobs?limit=10" \
-H "Authorization: Bearer $BEARER" | jq '.retry_jobs[] | {retry_job_id, status, total, succeeded, failed}'
# Single job detail
curl -sS "$BASE/crew/bookings/retry-jobs/JOB-..." \
-H "Authorization: Bearer $BEARER" | jq .
The same envelope is used everywhere:
{ "error": { "code": "<UPPER_SNAKE>", "message": "<human>", "details": [...] }, "status": <int> }
401 UNAUTHORIZED — missing / malformed bearercurl -sS "$BASE/crew/schedules" | jq .
# => { "error": { "code": "UNAUTHORIZED", ... }, "status": 401 }
404 NOT_FOUND — unknown id, or cross-tenant accesscurl -sS "$BASE/crew/bookings/BKG-DOES-NOT-EXIST" \
-H "Authorization: Bearer $BEARER" | jq .
Cross-tenant requests return 404, not 403. A resource owned by a different airline looks identical to a non-existent resource — we don't leak existence across tenants.
409 CONFLICT — duplicate booking on /bookings/requestA live booking already exists for the same anchors and booking type. Use PATCH /bookings/{id} to modify the existing one, or release it with manual_override: false first.
# Run the §3.2 hotel commit twice. The second call should return 409:
# { "error": { "code": "CONFLICT", "message": "a live HOTEL booking already exists for ..." }, "status": 409 }
/bookings/requestA flight_option_id is a supplier offer held for 20 minutes after the originating /flight-options query. Committing an expired offer doesn't pre-fail — the request is accepted (202) and the booking comes back NEEDS_REVIEW (the commit failed at the supplier). Re-search and pick a fresh option.
422 VALIDATION_FAILED — structural validationcurl -sS -X POST "$BASE/crew/schedules" \
-H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{ "pairings": [] }' | jq .
# => { "error": { "code": "VALIDATION_FAILED", ... }, "status": 422 }
For an automated airline-onboarding script:
#!/usr/bin/env bash
set -euo pipefail
: "${BASE:?set BASE first}"
: "${BEARER:?set BEARER first}"
call() {
curl -fsS -H "Authorization: Bearer $BEARER" -H "Content-Type: application/json" "$@"
}
echo "1/4 Push hotel config…"
call -X PUT "$BASE/crew/config/hotels" \
-d '{"bases":[{"base_station":"SLC","hotels":[{"property_id":"hilton-slc-airport","policy":"STANDARD","standard_check_in_time":"15:00","standard_check_out_time":"11:00","buffer_minutes":60}]}]}' >/dev/null
echo "2/4 Push flight rules…"
call -X PUT "$BASE/crew/config/flight-rules" \
-d '{"auto_book_max_price":{"amount":800.00,"currency":"USD"}}' >/dev/null
echo "3/4 Submit a smoke-test roster…"
SCHEDULE_ID=$(call -X POST "$BASE/crew/schedules" \
-d '{"external_ref":"SMOKE-TEST","as_of":"2026-06-15T08:00:00Z","schedule_start_date":"2026-06-14","schedule_end_date":"2026-06-15","pairings":[{"pairing_number":"P-SMOKE","origin_base":"SLC","crew":[{"employee_id":"EMP-SMOKE","name":"Smoke Test","home_base":"SLC","rank":"CA","traveller_email":"smoke@example.invalid"}],"duty_periods":[{"duty_period_num":1,"duty_date":"2026-06-14","fdp_start_local":"2026-06-14T16:00:00-06:00","fdp_end_local":"2026-06-14T20:45:00-04:00","legs":[{"leg_sequence":1,"activity_code":"FLT","from_airport":"SLC","to_airport":"JFK","dep_time_local":"2026-06-14T17:00:00-06:00","arr_time_local":"2026-06-14T20:45:00-04:00"}],"layover":{"station":"JFK","booking_required":true,"checkin_time_local":"2026-06-14T21:15:00-04:00","checkout_time_local":"2026-06-15T11:00:00-04:00"}},{"duty_period_num":2,"duty_date":"2026-06-15","fdp_start_local":"2026-06-15T13:15:00-04:00","fdp_end_local":"2026-06-15T16:30:00-06:00","legs":[{"leg_sequence":2,"activity_code":"FLT","from_airport":"JFK","to_airport":"SLC","dep_time_local":"2026-06-15T14:15:00-04:00","arr_time_local":"2026-06-15T16:30:00-06:00"}]}]}]}' \
| jq -r .schedule_id)
echo " schedule_id=$SCHEDULE_ID"
echo "4/4 Poll until COMPLETED (or BOOKING with all flight tasks terminal)…"
for i in 1 2 3 4 5 6 7 8 9 10; do
STATUS=$(call "$BASE/crew/schedules/$SCHEDULE_ID/processing-status" | jq -r .status)
echo " [$i] status=$STATUS"
case "$STATUS" in
COMPLETED|FAILED) break ;;
*) sleep 2 ;;
esac
done
echo "Done. Bookings:"
call "$BASE/crew/bookings?schedule_id=$SCHEDULE_ID" | jq '.bookings[] | {booking_id, type, status}'
Airline-industry terminology used in this API. Only terms that actually appear in the spec are listed here. Many field descriptions cross-reference these definitions.
CrewMember.CA Captain, FO First Officer, FA Flight Attendant, PU Purser, SO Second Officer. Airline-conventional. Maps to CrewMember.rank.CrewMember.home_base (IATA code).CrewMember.union_code.Hierarchy, largest to smallest: Roster (a crew member's monthly schedule) → Pairings (multi-day trips) → Duty Periods (one operational day each) → Legs (individual flight segments).
POST /schedules and the Schedule resource Routespring builds from it.Pairing; identified by pairing_number.DutyPeriod (duty_period_num, duty_date, fdp_start_local, fdp_end_local).leg_sequence). Maps to Leg.A leg's activity_code says what the crew is doing during that leg (industry-conventional, not regulated):
| Code | Meaning | Counts as duty? |
|---|---|---|
FLT |
Revenue flight — crew is operating | Yes |
DH |
Deadhead — see below | Yes (duty, not rest) |
TRG |
Training (sim, ground school, line check) | Yes |
OFF |
Scheduled day off | No |
RSV |
Reserve — crew on call for assignment | Yes |
SBY |
Standby — available for immediate call-out | Yes |
SL |
Sick leave | No |
RST |
Rest period at layover hotel | No |
Leg.block_hours.DutyPeriod.fdp_start_local / fdp_end_local.DutyPeriod.layover.Pairing.tafb_hours.B777, A350, B737; ICAO/IATA codes). Tail is the unique registration of one airframe (A6-EBA). Maps to Leg.aircraft_type / Leg.aircraft_tail.P1/P2) — typically only hours to react.property_id returns 422. DIRECT hotels bypass GDS and are managed by the airline directly.pnr, ticket_id, transaction_id, and trip_session_id on a FLIGHT booking after the booking is confirmed and the status is refreshed via GET /crew/bookings/{id}/status.Four conceptual domains:
POST /schedules (JSON). Routespring computes
the diff server-side against the prior submission and derives booking
action items.
There is no delta mode — always send everything you have open.Booking.config_version_used) so historical
bookings can be replayed against the exact rules that were in force.Action items reach the booking engine via two distinct paths. Both produce the same downstream artifacts: action items with the same classification taxonomy, bookings with the same lifecycle, the same retry semantics.
| Path | When to use | Endpoint(s) |
|---|---|---|
| Schedule diff | Normal flow — airline publishes the roster, Routespring derives every needed booking from the diff against the prior submission | POST /schedules |
| Airline injection | IROPs and ad-hoc requirements where the airline already knows what's needed and the roster system hasn't caught up | POST /action-items (let engine search + auto-book) or POST /bookings/request (airline already picked the option) |
Use Booking.source and ActionItem.source (SCHEDULE_DIFF /
AIRLINE_INJECTED / IROP) to distinguish the origin downstream.
Crew, pairing, and leg identity all come from the airline's own
identifiers — Routespring does not mint a parallel crew_id /
pairing_id / duty_id / leg_id. Every booking record is denormalized
with these keys so retrieval by any of them is a single indexed lookup:
| Anchor | Issued by | Notes |
|---|---|---|
employee_id |
Airline | Your HR system's employee id — the crew anchor |
pairing_number |
Airline | e.g. MX-20260527-4821 |
flight_number |
Airline | e.g. MX401 |
irop_ref |
Airline | IROP correlation id for IROP-injected artifacts |
schedule_id |
Routespring | The submission id returned by POST /schedules |
Query GET /bookings?<any-key>=<value> and get the same record set
regardless of which anchor you pass.
POST /schedules returns 202 Accepted immediately. Outcomes surface
asynchronously at three levels — track all three:
┌──────────────────────────────────────────────────────────────────┐
│ 1. Submission level — ProcessingStatus │
│ QUEUED → PROCESSING → BOOKING → COMPLETED │
│ └─ or FAILED (file unparseable) │
│ │
│ 2. Action item level — ActionItem.disposition │
│ AUTO_BOOKED | AUTO_CANCELLED | PENDING_REVIEW | FAILED │
│ │
│ 3. Booking level — Booking.status │
│ PENDING → CONFIRMED → MODIFIED → CANCELLED │
│ └─ or FAILED / NEEDS_REVIEW │
└──────────────────────────────────────────────────────────────────┘
ProcessingStatus: COMPLETED does not imply every action succeeded —
individual bookings can still be FAILED. Always check the booking
level too. The schedule-level summary.action_items_* counts give you
the breakdown at a glance.
Three closely-related fields that confuse first-time readers:
ActionItem.disposition (AUTO_BOOKED / AUTO_CANCELLED / PENDING_REVIEW
/ FAILED) — what the engine decided to do with the action.ActionItem.auto_eligible (boolean) — whether the action passed all
auto-book eligibility checks (preferred vendor available, within
auto_book_max_price, etc.). Independent of
supplier success. An action can be auto_eligible: true but still
end up disposition: FAILED if the supplier call failed — a retry
could succeed. Use this to distinguish "ineligible, don't retry" from
"eligible but supplier failed, retry might work".Booking.status — the booking's lifecycle state at the supplier.Any of these actions sets Booking.manual_override = true automatically:
PATCH /bookings/{id}, DELETE /bookings/{id}, POST /bookings/request,
POST /action-items. Once manual_override is true, the diff engine
will NOT modify or cancel this booking on subsequent schedule
submissions, even if the upstream pairing changes.
This prevents the diff engine from silently reverting a deliberate
manual change (e.g. an IROP rebooking made before the catch-up roster
arrives). To release a booking back to automatic management, PATCH
with {"manual_override": false} — the diff engine resumes ownership
on the next submission.
A cancelled booking whose pairing later reappears in a schedule will not auto-recreate — re-issue explicitly.
ActionItem.classification describes why an action exists. The
taxonomy (new values may be added in future versions — handle unknown values gracefully):
| Group | Values |
|---|---|
| Hotel — diff-derived | NEW_LAYOVER, LAYOVER_DATE_CHANGE, LAYOVER_CITY_CHANGE, LAYOVER_CANCELLED, LAYOVER_EXTENDED |
| Flight — diff-derived | NEW_DEADHEAD, DEADHEAD_TIME_CHANGE, DEADHEAD_CANCELLED |
| Pairing-level | PAIRING_NEW, PAIRING_EXTENDED, PAIRING_SHORTENED, PAIRING_CANCELLED, SWAP_OUT, SWAP_IN, STATUS_TRANSITION |
| IROP — airline-injected | IROP_NEW_DEADHEAD, IROP_REROUTE, IROP_HOTEL_EXTEND, IROP_HOTEL_CANCEL |
| Data quality | DATA_QUALITY_FLAG, DATA_QUALITY_QUARANTINE, DUPLICATE_ROW |
| No-op | UNCHANGED (only when the Routespring-managed emit_nop_tasks toggle is on — off by default in v1, not set via this API) |
v1 diff-derived subset. A POST /schedules diff in v1 derives the
hotel, deadhead, swap, and data-quality classifications — NEW_LAYOVER,
LAYOVER_DATE_CHANGE, LAYOVER_CITY_CHANGE, LAYOVER_EXTENDED,
LAYOVER_CANCELLED, NEW_DEADHEAD, DEADHEAD_TIME_CHANGE,
DEADHEAD_CANCELLED, SWAP_OUT, SWAP_IN, DUPLICATE_ROW,
DATA_QUALITY_FLAG, and UNCHANGED. Pairing-level
classifications are not emitted by the diff path. The IROP_*
values are airline-supplied on
POST /action-items / POST /bookings/request and are accepted in v1.
ActionItem.priority drives queue ordering and human-review SLAs:
| Priority | Typical use | Target time to disposition |
|---|---|---|
P1 |
IROPs requiring immediate action — flights departing in hours | ~5 minutes |
P2 |
Same-day pairing changes; default for airline injections | ~30 minutes |
P3 |
Routine layover bookings several days out | within the hour |
P4 |
Backfills, no-op annotations, low-urgency cleanup | next batch window |
Airline injections (POST /action-items) default to P2. Bump to P1
for IROPs with tight SLAs.
v1: priority is recorded on every action item, but review-SLA
enforcement is not active — the target times above are advisory.
All endpoints require Authorization: Bearer <access_token>. The token is
obtained via an OAuth2 client_credentials flow on the Routespring auth
service — see Quickstart §0 ("Get an access token") for the full sequence:
client_id + client_secret.POST /oauth/token — Basic client_id:client_secret,
grant_type=client_credentials → a short-lived JWT access_token
(expires_in ≈ 15 min) plus a refresh_token.POST /oauth/refresh — exchange the refresh_token for a new
access token when it expires.The access token resolves to the calling airline (tenant); there is no
airline id in the URL path — every request is scoped to the company the
token belongs to. An expired token returns 401 UNAUTHORIZED.
The Schedules APIs are where every booking workflow begins. Submit your airline's current roster as a complete snapshot — Routespring compares it against the bookings that already exist in the system and derives everything that needs to happen: new bookings, cancellations, modifications, and crew swaps.
You do not send deltas. Every submission is the full picture of your open pairings for a given date window. Routespring handles the reconciliation server-side, so your integration stays simple — re-export from your crew management system and POST.
Processing is asynchronous. The submit returns immediately with a
schedule_id; use the processing-status endpoint to track progress. The
booking decisions Routespring derives are surfaced as Action Items — see the
Action Items section for how to retrieve and act on them.
Push the airline's roster to Routespring as a JSON payload. Each submission represents the complete current state of all open pairings — Routespring computes the diff against the prior submission server-side and derives action items accordingly.
Returns 202 immediately with a schedule_id; diffing,
classification, and booking run asynchronously. Track outcomes
at the three async levels described in the Async outcome
model section of the API overview. Briefly: a submission of
200 duty periods where 3 individual hotel bookings fail surfaces
as ProcessingStatus: COMPLETED plus 197 CONFIRMED and 3
FAILED bookings, each retrievable individually.
JSON is the only ingestion format: the airline-workflow anchors
(pairing_number, employee_id, …) flow through as structured
fields tied to your operational vocabulary.
| external_ref | string Airline-side identifier for this submission (echoed back). |
| as_of | string <date-time> Airline timestamp (UTC) when this snapshot was captured. |
| schedule_start_date required | string <date> Inclusive start of the schedule window this submission covers
( |
| schedule_end_date required | string <date> Inclusive end of the schedule window this submission covers
( |
required | Array of objects (Pairing) non-empty |
{- "external_ref": "MX-20260527-4821",
- "as_of": "2026-05-27T08:00:00Z",
- "schedule_start_date": "2026-05-16",
- "schedule_end_date": "2026-05-31",
- "pairings": [
- {
- "pairing_number": "MX-20260527-4821",
- "origin_base": "SLC",
- "terminus_base": "SLC",
- "check_in_local": "2026-05-26T23:30:00-06:00",
- "check_out_local": "2026-05-29T04:00:00-06:00",
- "tafb_hours": 52.5,
- "crew": [
- {
- "employee_id": "EMP-1042",
- "name": "Jordan Reyes",
- "rank": "CA",
- "home_base": "SLC",
- "traveller_email": "jreyes@flymx.com",
- "union_code": "ALPA"
}, - {
- "employee_id": "EMP-2317",
- "name": "Alex Pham",
- "rank": "FO",
- "home_base": "SLC",
- "traveller_email": "apham@flymx.com",
- "union_code": "ALPA"
}
], - "duty_periods": [
- {
- "duty_period_num": 1,
- "duty_date": "2026-05-27",
- "fdp_start_local": "2026-05-26T23:30:00-06:00",
- "fdp_end_local": "2026-05-27T12:15:00-04:00",
- "legs": [
- {
- "leg_sequence": 1,
- "activity_code": "FLT",
- "flight_number": "MX401",
- "from_airport": "SLC",
- "to_airport": "LAX",
- "dep_time_local": "2026-05-27T01:00:00-06:00",
- "arr_time_local": "2026-05-27T01:30:00-07:00",
- "aircraft_type": "A220-300",
- "aircraft_tail": "N412MX"
}, - {
- "leg_sequence": 2,
- "activity_code": "FLT",
- "flight_number": "MX588",
- "from_airport": "LAX",
- "to_airport": "IAD",
- "dep_time_local": "2026-05-27T03:00:00-07:00",
- "arr_time_local": "2026-05-27T14:15:00-04:00",
- "aircraft_type": "A220-300",
- "aircraft_tail": "N412MX"
}
], - "layover": {
- "station": "IAD",
- "booking_required": true,
- "checkin_time_local": "2026-05-27T14:45:00-04:00",
- "checkout_time_local": "2026-05-28T01:00:00-04:00"
}
}, - {
- "duty_period_num": 2,
- "duty_date": "2026-05-28",
- "fdp_start_local": "2026-05-28T02:00:00-04:00",
- "fdp_end_local": "2026-05-28T09:30:00-05:00",
- "legs": [
- {
- "leg_sequence": 3,
- "activity_code": "FLT",
- "flight_number": "MX210",
- "from_airport": "IAD",
- "to_airport": "TUL",
- "dep_time_local": "2026-05-28T04:00:00-04:00",
- "arr_time_local": "2026-05-28T05:45:00-05:00",
- "aircraft_type": "A220-300",
- "aircraft_tail": "N388MX"
}, - {
- "leg_sequence": 4,
- "activity_code": "DH",
- "from_airport": "TUL",
- "to_airport": "DAL",
- "expected_dep_time_local": "2026-05-28T07:00:00-05:00",
- "expected_arr_time_local": "2026-05-28T08:15:00-05:00",
- "booking_required": true
}
], - "layover": {
- "station": "DAL",
- "booking_required": true,
- "checkin_time_local": "2026-05-28T08:45:00-05:00",
- "checkout_time_local": "2026-05-29T00:30:00-05:00"
}
}, - {
- "duty_period_num": 3,
- "duty_date": "2026-05-29",
- "fdp_start_local": "2026-05-29T01:30:00-05:00",
- "fdp_end_local": "2026-05-29T04:00:00-06:00",
- "legs": [
- {
- "leg_sequence": 5,
- "activity_code": "FLT",
- "flight_number": "MX305",
- "from_airport": "DAL",
- "to_airport": "SLC",
- "dep_time_local": "2026-05-29T03:00:00-05:00",
- "arr_time_local": "2026-05-29T03:45:00-06:00",
- "aircraft_type": "A220-300",
- "aircraft_tail": "N401MX"
}
]
}
]
}
]
}{- "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
- "external_ref": "NB-EXPORT-2026-05-15T14:30Z",
- "processing_status": "QUEUED",
- "received_at": "2026-05-15T14:32:11Z",
- "links": {
- "status": "/v1/crew/schedules/sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M/processing-status",
- "action_items": "/v1/crew/schedules/sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M/action-items"
}
}Returns a paginated list of schedule submissions for your airline,
ordered by submission time descending (most recent first). Use this
to look up a schedule_id when you don't have it from the original
submit response, or to audit submission history.
No filter is required — omitting all parameters returns the most recent 50 submissions.
| from | string <date> Filter schedules received on/after this date. |
| to | string <date> Filter schedules received on/before this date. |
| status | string (ProcessingStatusEnum) Enum: "QUEUED" "PROCESSING" "BOOKING" "COMPLETED" "FAILED" Filter by processing status. Useful for finding submissions still
in progress ( |
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (ScheduleDetail) | |
| next_cursor | string or null |
| total_results | integer Total schedules matching the filter across all pages. |
{- "schedules": [
- {
- "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
- "external_ref": "NB-EXPORT-2026-05-15T14:30Z",
- "processing_status": "COMPLETED",
- "received_at": "2026-05-15T14:32:11Z",
- "started_at": "2026-05-15T14:32:13Z",
- "completed_at": "2026-05-15T14:32:48Z",
- "links": {
- "status": "/v1/crew/schedules/sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M/processing-status",
- "action_items": "/v1/crew/schedules/sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M/action-items"
}, - "summary": {
- "action_items_total": 67,
- "action_items_auto_booked": 54,
- "action_items_auto_cancelled": 0,
- "action_items_pending_review": 13,
- "action_items_failed": 0,
- "audit_events": 4
}
}, - {
- "schedule_id": "sch_01HMWX8K2P3TL7N4BKDQRS3E8L",
- "external_ref": "NB-EXPORT-2026-05-14T09:15Z",
- "processing_status": "COMPLETED",
- "received_at": "2026-05-14T09:18:42Z",
- "started_at": "2026-05-14T09:18:44Z",
- "completed_at": "2026-05-14T09:19:21Z",
- "links": {
- "status": "/v1/crew/schedules/sch_01HMWX8K2P3TL7N4BKDQRS3E8L/processing-status",
- "action_items": "/v1/crew/schedules/sch_01HMWX8K2P3TL7N4BKDQRS3E8L/action-items"
}, - "summary": {
- "action_items_total": 12,
- "action_items_auto_booked": 10,
- "action_items_auto_cancelled": 0,
- "action_items_pending_review": 2,
- "action_items_failed": 0,
- "audit_events": 1
}
}
], - "next_cursor": null,
- "total_results": 2
}Returns the full metadata and processing summary for a single schedule submission. Use this to check the outcome of a submission — how action items were distributed across auto-booked, pending review, and failed.
| schedule_id required | string Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M |
| schedule_id | string |
| external_ref | string |
| processing_status | string (ProcessingStatusEnum) Enum: "QUEUED" "PROCESSING" "BOOKING" "COMPLETED" "FAILED" Lifecycle of a schedule submission:
|
| received_at | string <date-time> |
object | |
| started_at | string <date-time> |
| completed_at | string <date-time> |
object (ScheduleSummary) |
{- "schedule_id": "string",
- "external_ref": "string",
- "processing_status": "QUEUED",
- "received_at": "2019-08-24T14:15:22Z",
- "links": {
- "status": "string",
- "action_items": "string"
}, - "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "summary": {
- "action_items_total": 0,
- "action_items_auto_booked": 0,
- "action_items_auto_cancelled": 0,
- "action_items_pending_review": 0,
- "action_items_failed": 0,
- "audit_events": 0
}
}| schedule_id required | string Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M |
| schedule_id | string |
| status | string (ProcessingStatusEnum) Enum: "QUEUED" "PROCESSING" "BOOKING" "COMPLETED" "FAILED" Lifecycle of a schedule submission:
|
| received_at | string <date-time> |
| started_at | string or null <date-time> |
| completed_at | string or null <date-time> |
| error | string or null |
| flight_tasks_pending | integer Count of FLIGHT (deadhead) booking tasks not yet terminal. The
submission stays in |
| poll_recommended | boolean Whether the client should keep polling this endpoint. Use this as the loop condition instead of hard-coding status checks.
|
object (ScheduleSummary) |
{- "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
- "status": "COMPLETED",
- "received_at": "2026-05-15T14:32:11Z",
- "started_at": "2026-05-15T14:32:13Z",
- "completed_at": "2026-05-15T14:32:48Z",
- "summary": {
- "action_items_total": 67,
- "action_items_auto_booked": 54,
- "action_items_auto_cancelled": 0,
- "action_items_pending_review": 13,
- "audit_events": 4
}
}Flat CSV of every action item from this schedule submission. One row per action. Machine-readable; suitable for ingestion into airline-side BI / analytics systems.
| schedule_id required | string Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M |
{- "error": {
- "code": "INVALID_REQUEST",
- "message": "string",
- "details": [
- {
- "field": "string",
- "issue": "string"
}
]
}, - "status": 0
}Returns your airline's active hotel configuration — the preferred hotel per crew base, with its contract rate and check-in/out policy. The booking engine uses the hotel configured for the crew member's layover city, matching against the contracted rate.
To retrieve a historical snapshot, use
GET /crew/config/hotels/versions/{version}.
required | Array of objects (HotelBaseConfig) |
| version | string |
| active | boolean |
| updated_at | string <date-time> |
{- "bases": [
- {
- "base_station": "SLC",
- "hotels": [
- {
- "property_id": "hilton-slc-airport",
- "chain": "HILTON",
- "policy": "STANDARD",
- "standard_check_in_time": "15:00",
- "standard_check_out_time": "11:00",
- "buffer_minutes": 60,
- "contract_rate": {
- "amount": 145,
- "currency": "USD"
}, - "rate_periods": [
- {
- "effective_from": "2026-01-01",
- "effective_to": "2026-06-30",
- "rate": {
- "amount": 149,
- "currency": "USD"
}
}, - {
- "effective_from": "2026-07-01",
- "effective_to": "2026-12-31",
- "rate": {
- "amount": 169,
- "currency": "USD"
}
}
]
}
]
}, - {
- "base_station": "DXB",
- "hotels": [
- {
- "property_id": "jw-marriott-dxb",
- "chain": "MARRIOTT",
- "policy": "GUARANTEED",
- "guaranteed_early_check_in_time": "10:00",
- "guaranteed_late_check_out_time": "14:00",
- "buffer_minutes": 30,
- "contract_rate": {
- "amount": 220,
- "currency": "USD"
}
}
]
}
], - "version": "v-2026-05-15-001",
- "active": true,
- "updated_at": "2026-05-15T10:32:00Z"
}Replaces your airline's hotel configuration. The full list of bases and hotels must be sent — this is a full replacement, not a merge. One hotel per base. The previous config is archived as an immutable version; every booking records the config version active at the time it was made.
The booking channel is derived from each property_id: a match in
Routespring's GDS inventory (look it up via GET /hotels) books via GDS;
a property_id that is not in the inventory is a DIRECT hotel only
when it supplies its own chain. A non-inventory property_id with no
chain is treated as a mistyped GDS id and rejected with
422 UNKNOWN_HOTEL_PROPERTY. handler_type is therefore not sent on
input — it is returned (derived) on the config response.
Rates are not set here. Omitting rate_periods leaves a hotel's rates
untouched; an explicit [] clears legacy ones; a non-empty list is refused
with 422 CONTRACT_REQUIRED_FOR_RATE, because a rate belongs to a contract
(POST …/contracts, then PUT …/contracts/{contract_id}/rates).
A hotel that already has contracts cannot be removed by omitting it from
the payload, and its terms cannot be changed while automated booking is
switched on for it; both return 409.
required | Array of objects (HotelBaseConfig) |
required | Array of objects (HotelBaseConfig) |
| version | string |
| active | boolean |
| updated_at | string <date-time> |
{- "bases": [
- {
- "base_station": "SLC",
- "hotels": [
- {
- "property_id": "hilton-slc-airport",
- "policy": "STANDARD",
- "standard_check_in_time": "15:00",
- "standard_check_out_time": "11:00",
- "buffer_minutes": 60,
- "contract_rate": {
- "amount": 145,
- "currency": "USD"
}, - "rate_periods": [
- {
- "effective_from": "2026-01-01",
- "effective_to": "2026-06-30",
- "rate": {
- "amount": 149,
- "currency": "USD"
}
}, - {
- "effective_from": "2026-07-01",
- "effective_to": "2026-12-31",
- "rate": {
- "amount": 169,
- "currency": "USD"
}
}
]
}
]
}, - {
- "base_station": "DXB",
- "hotels": [
- {
- "property_id": "jw-marriott-dxb",
- "chain": "MARRIOTT",
- "policy": "GUARANTEED",
- "guaranteed_early_check_in_time": "10:00",
- "guaranteed_late_check_out_time": "14:00",
- "buffer_minutes": 30,
- "contract_rate": {
- "amount": 220,
- "currency": "USD"
}
}
]
}
]
}{- "bases": [
- {
- "base_station": "SLC",
- "hotels": [
- {
- "property_id": "hilton-slc-airport",
- "chain": "HILTON",
- "policy": "STANDARD",
- "standard_check_in_time": "15:00",
- "standard_check_out_time": "11:00",
- "buffer_minutes": 60,
- "contract_rate": {
- "amount": 145,
- "currency": "USD"
}, - "rate_periods": [
- {
- "effective_from": "2026-01-01",
- "effective_to": "2026-06-30",
- "rate": {
- "amount": 149,
- "currency": "USD"
}
}, - {
- "effective_from": "2026-07-01",
- "effective_to": "2026-12-31",
- "rate": {
- "amount": 169,
- "currency": "USD"
}
}
]
}
]
}, - {
- "base_station": "DXB",
- "hotels": [
- {
- "property_id": "jw-marriott-dxb",
- "chain": "MARRIOTT",
- "policy": "GUARANTEED",
- "guaranteed_early_check_in_time": "10:00",
- "guaranteed_late_check_out_time": "14:00",
- "buffer_minutes": 30,
- "contract_rate": {
- "amount": 220,
- "currency": "USD"
}
}
]
}
], - "version": "v-2026-05-16-001",
- "active": true,
- "updated_at": "2026-05-16T09:15:00Z"
}Enable / disable / remove requests raised for your company, newest first. A request changes nothing on the hotel until Routespring approves it.
| open_only | boolean Default: true When |
Array of objects (HotelChangeRequest) |
{- "change_requests": [
- {
- "request_id": "HMR-3f9a1c22",
- "hotel_config_id": 0,
- "contract_id": 0,
- "intent": "ENABLE",
- "status": "OPEN",
- "hotel_name": "string",
- "station": "DAL",
- "contract_number": "string",
- "contract_from": "2019-08-24",
- "contract_to": "2019-08-24",
- "requested_by": "user@example.com",
- "requested_on": "2019-08-24T14:15:22Z",
- "decided_by": "user@example.com",
- "decided_on": "2019-08-24T14:15:22Z",
- "decision_note": "string"
}
]
}Withdraws a request that Routespring has not yet decided. Nothing on the hotel changes.
| request_id required | string Example: HMR-3f9a1c22 |
| request_id | string |
| hotel_config_id | integer <int64> |
| contract_id | integer <int64> |
| intent | string Enum: "ENABLE" "DISABLE" "DELETE"
|
| status | string Enum: "OPEN" "APPROVED" "DECLINED" "WITHDRAWN" |
| hotel_name | string |
| station | string |
| contract_number | string |
| contract_from | string <date> |
| contract_to | string <date> |
| requested_by | string <email> |
| requested_on | string <date-time> |
| decided_by | string <email> |
| decided_on | string <date-time> |
| decision_note | string |
{- "request_id": "HMR-3f9a1c22",
- "hotel_config_id": 0,
- "contract_id": 0,
- "intent": "ENABLE",
- "status": "OPEN",
- "hotel_name": "string",
- "station": "DAL",
- "contract_number": "string",
- "contract_from": "2019-08-24",
- "contract_to": "2019-08-24",
- "requested_by": "user@example.com",
- "requested_on": "2019-08-24T14:15:22Z",
- "decided_by": "user@example.com",
- "decided_on": "2019-08-24T14:15:22Z",
- "decision_note": "string"
}Switching automated booking on (ENABLE) or off (DISABLE) is a
reviewed action: this records the request and notifies the Routespring
team, and changes nothing on the hotel. Poll
GET /crew/config/hotel-change-requests for the outcome.
A request is about a contract, not the hotel — so one open request per
contract, not per hotel. That lets you ask about next season's contract
while a request about the one currently booking is still open. Omit
contract_id and the contract is resolved for you: the hotel's switched-off
contract for ENABLE, its switched-on contract for DISABLE.
ENABLE on a contract that has already ended is refused here rather than
at review — approval could only ever decline it. Add the renewal contract,
price it, and request that one instead.
| hotel_config_id required | integer <int64> |
| intent required | string Enum: "ENABLE" "DISABLE"
|
| contract_id | integer <int64> Which contract the request is about. Optional only while the hotel has a
single contract; with more than one (a renewal beside the live contract
is the normal state) it is required and the call returns |
| note | string Free text for the reviewer. |
{- "intent": "ENABLE",
- "contract_id": 0,
- "note": "string"
}{- "request_id": "HMR-3f9a1c22",
- "hotel_config_id": 0,
- "contract_id": 0,
- "intent": "ENABLE",
- "status": "OPEN",
- "hotel_name": "string",
- "station": "DAL",
- "contract_number": "string",
- "contract_from": "2019-08-24",
- "contract_to": "2019-08-24",
- "requested_by": "user@example.com",
- "requested_on": "2019-08-24T14:15:22Z",
- "decided_by": "user@example.com",
- "decided_on": "2019-08-24T14:15:22Z",
- "decision_note": "string"
}Every contract on the hotel, each with its rate line items nested and a
fully_covered flag showing whether those rates tile the contract
window exactly. Only a contract that is fully covered, enabled and
unexpired can price a stay.
| hotel_config_id required | integer <int64> |
Array of objects (HotelContract) |
{- "contracts": [
- {
- "contract_id": 0,
- "hotel_config_id": 0,
- "station": "DAL",
- "contract_number": "string",
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "notes": "string",
- "automated_booking_enabled": true,
- "fully_covered": true,
- "rate_count": 0,
- "rates": [
- {
- "rate_id": 0,
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "enabled": true,
- "notes": "string"
}
], - "has_document": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Creates the contract record that makes a hotel bookable. A hotel with
no contract cannot be auto-booked — its layovers come back
NEEDS_REVIEW — because only contract-linked rates price a night.
effective_from and effective_to are mandatory and must not overlap
another contract on the same hotel. Add the rates next with
PUT …/contracts/{contract_id}/rates.
The signed contract document is managed in the Hotel Manager UI and is not part of this API.
| hotel_config_id required | integer <int64> |
| effective_from required | string <date> First day the contract is in force. Must not overlap another contract on this hotel. |
| effective_to required | string <date> Last day the contract is in force. |
| contract_number | string The airline's own reference for the signed agreement. |
| notes | string |
| contract_id | integer <int64> |
| hotel_config_id | integer <int64> |
| station | string |
| contract_number | string |
| effective_from | string <date> |
| effective_to | string <date> |
| notes | string |
| automated_booking_enabled | boolean Whether this contract is switched on for automated booking. Changed only through a change request. |
| fully_covered | boolean Whether the contract's rate line items tile its window exactly — the
readiness signal to check before asking for the contract to be switched
on. Independent of |
| rate_count | integer |
Array of objects (ContractRate) | |
| has_document | boolean Whether a signed document is attached. Documents are managed in the Hotel Manager UI. |
| created_at | string <date-time> |
| updated_at | string <date-time> |
{- "effective_from": "2026-09-01",
- "effective_to": "2027-08-31",
- "contract_number": "BRZ-DAL-2026-14",
- "notes": "string"
}{- "contract_id": 0,
- "hotel_config_id": 0,
- "station": "DAL",
- "contract_number": "string",
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "notes": "string",
- "automated_booking_enabled": true,
- "fully_covered": true,
- "rate_count": 0,
- "rates": [
- {
- "rate_id": 0,
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "enabled": true,
- "notes": "string"
}
], - "has_document": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Edits contract_number, effective_from, effective_to or notes.
Changing the window invalidates rate coverage — the contract stops
pricing until its rates tile the new window exactly.
| hotel_config_id required | integer <int64> |
| contract_id required | integer <int64> |
| effective_from | string <date> |
| effective_to | string <date> |
| contract_number | string |
| notes | string |
| contract_id | integer <int64> |
| hotel_config_id | integer <int64> |
| station | string |
| contract_number | string |
| effective_from | string <date> |
| effective_to | string <date> |
| notes | string |
| automated_booking_enabled | boolean Whether this contract is switched on for automated booking. Changed only through a change request. |
| fully_covered | boolean Whether the contract's rate line items tile its window exactly — the
readiness signal to check before asking for the contract to be switched
on. Independent of |
| rate_count | integer |
Array of objects (ContractRate) | |
| has_document | boolean Whether a signed document is attached. Documents are managed in the Hotel Manager UI. |
| created_at | string <date-time> |
| updated_at | string <date-time> |
{- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "contract_number": "string",
- "notes": "string"
}{- "contract_id": 0,
- "hotel_config_id": 0,
- "station": "DAL",
- "contract_number": "string",
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "notes": "string",
- "automated_booking_enabled": true,
- "fully_covered": true,
- "rate_count": 0,
- "rates": [
- {
- "rate_id": 0,
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "enabled": true,
- "notes": "string"
}
], - "has_document": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Removes the contract and unlinks its rates, which therefore stop pricing. Refused while the hotel is live for crew booking — pause it first via a change request.
| hotel_config_id required | integer <int64> |
| contract_id required | integer <int64> |
{- "error": {
- "code": "INVALID_REQUEST",
- "message": "string",
- "details": [
- {
- "field": "string",
- "issue": "string"
}
]
}, - "status": 0
}| hotel_config_id required | integer <int64> |
| contract_id required | integer <int64> |
Array of objects (ContractRate) | |
| fully_covered | boolean Whether these rates tile the contract window exactly. |
{- "rates": [
- {
- "rate_id": 0,
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "enabled": true,
- "notes": "string"
}
], - "fully_covered": true
}Replaces the contract's rates as a set. Combined they must cover the contract window exactly — no gaps, no overlaps — or the call is rejected and nothing changes. A nightly rate must be greater than zero; a zero or negative rate prices nothing and would leave those nights uncovered.
| hotel_config_id required | integer <int64> |
| contract_id required | integer <int64> |
required | Array of objects non-empty |
Array of objects (ContractRate) | |
| fully_covered | boolean |
{- "rates": [
- {
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "notes": "string"
}
]
}{- "rates": [
- {
- "rate_id": 0,
- "effective_from": "2019-08-24",
- "effective_to": "2019-08-24",
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "refundable": true,
- "cancel_days": 0,
- "enabled": true,
- "notes": "string"
}
], - "fully_covered": true
}Clears the whole rate set. Refused while the hotel is live for crew booking, since it would silently make every future stay unpriceable.
| hotel_config_id required | integer <int64> |
| contract_id required | integer <int64> |
{- "error": {
- "code": "INVALID_REQUEST",
- "message": "string",
- "details": [
- {
- "field": "string",
- "issue": "string"
}
]
}, - "status": 0
}Returns your airline's carrier and cabin preferences for deadhead positioning flights. The booking engine applies these when searching for and selecting flights on behalf of crew.
| preferred_airlines | Array of strings IATA 2-letter carrier codes to prioritize when booking deadhead flights. The engine tries these carriers first; if none have availability, any non-blacklisted carrier is considered. |
| blacklisted_airlines | Array of strings IATA 2-letter carrier codes that must never be booked for deadhead positioning, regardless of availability or price. |
| preferred_cabin | string Enum: "ECONOMY" "PREMIUM_ECONOMY" "BUSINESS" "FIRST" Default cabin class for deadhead flights. The engine will not book
above this class. Can be overridden per-request via the
|
| same_carrier_preference | boolean When true, the engine prioritizes the crew member's own airline's
flights for deadhead positioning where available, before considering
carriers in |
| version | string |
| updated_at | string <date-time> |
{- "preferred_airlines": [
- "AA",
- "DL",
- "UA",
- "EK"
], - "blacklisted_airlines": [
- "F9"
], - "preferred_cabin": "ECONOMY",
- "same_carrier_preference": true,
- "version": "v-2026-05-15-001",
- "updated_at": "2026-05-15T10:36:00Z"
}Sets your airline's carrier and cabin preferences for deadhead positioning flights. Blacklisted carriers are never booked. Preferred carriers are tried first; if none have availability, the engine considers any non-blacklisted option.
Set same_carrier_preference: true to prioritize your own airline's flights
when repositioning crew.
| preferred_airlines | Array of strings IATA 2-letter carrier codes to prioritize when booking deadhead flights. The engine tries these carriers first; if none have availability, any non-blacklisted carrier is considered. |
| blacklisted_airlines | Array of strings IATA 2-letter carrier codes that must never be booked for deadhead positioning, regardless of availability or price. |
| preferred_cabin | string Enum: "ECONOMY" "PREMIUM_ECONOMY" "BUSINESS" "FIRST" Default cabin class for deadhead flights. The engine will not book
above this class. Can be overridden per-request via the
|
| same_carrier_preference | boolean When true, the engine prioritizes the crew member's own airline's
flights for deadhead positioning where available, before considering
carriers in |
| preferred_airlines | Array of strings IATA 2-letter carrier codes to prioritize when booking deadhead flights. The engine tries these carriers first; if none have availability, any non-blacklisted carrier is considered. |
| blacklisted_airlines | Array of strings IATA 2-letter carrier codes that must never be booked for deadhead positioning, regardless of availability or price. |
| preferred_cabin | string Enum: "ECONOMY" "PREMIUM_ECONOMY" "BUSINESS" "FIRST" Default cabin class for deadhead flights. The engine will not book
above this class. Can be overridden per-request via the
|
| same_carrier_preference | boolean When true, the engine prioritizes the crew member's own airline's
flights for deadhead positioning where available, before considering
carriers in |
| version | string |
| updated_at | string <date-time> |
{- "preferred_airlines": [
- "AA",
- "DL",
- "UA",
- "EK"
], - "blacklisted_airlines": [
- "F9"
], - "preferred_cabin": "ECONOMY",
- "same_carrier_preference": true
}{- "preferred_airlines": [
- "AA",
- "DL",
- "UA",
- "EK"
], - "blacklisted_airlines": [
- "F9"
], - "preferred_cabin": "ECONOMY",
- "same_carrier_preference": true,
- "version": "v-2026-05-16-001",
- "updated_at": "2026-05-16T09:20:00Z"
}Returns the price ceiling for auto-booking positioning (deadhead) flights.
If a booking lands in NEEDS_REVIEW unexpectedly, check auto_book_max_price
— fares above this amount are held for travel manager approval rather than
booked automatically.
object (Money) Maximum fare per ticket the engine will auto-book without human review.
If the cheapest available option exceeds this amount, the booking is
held as | |
| version | string Identifier of this config version, referenced by bookings via |
| updated_at | string <date-time> Timestamp when this version was created. |
{- "auto_book_max_price": {
- "amount": 145,
- "currency": "USD"
}, - "version": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}auto_book_max_price is the per-ticket ceiling for auto-booking a
positioning (deadhead) flight — fares above this are held as
NEEDS_REVIEW for travel manager approval rather than booked automatically.
Only fields included in the request are updated; omitted fields retain their current values.
object (Money) Maximum fare per ticket the engine will auto-book without human review.
If the cheapest available option exceeds this amount, the booking is
held as |
object (Money) Maximum fare per ticket the engine will auto-book without human review.
If the cheapest available option exceeds this amount, the booking is
held as | |
| version | string Identifier of this config version, referenced by bookings via |
| updated_at | string <date-time> Timestamp when this version was created. |
{- "auto_book_max_price": {
- "amount": 800,
- "currency": "USD"
}
}{- "auto_book_max_price": {
- "amount": 800,
- "currency": "USD"
}, - "version": "v-2026-05-16-001",
- "updated_at": "2026-05-16T09:22:00Z"
}Returns your airline's automation settings — the flags that control what Routespring executes automatically versus holds for human review, the account under which engine-driven bookings are placed, and the rate matching mode that determines how strictly contracted rates are enforced at booking time.
When an automation flag is disabled, Routespring creates an action item for your team to handle instead of executing automatically. See the Action Items section for how these are surfaced and managed.
If bookings are landing in NEEDS_REVIEW unexpectedly, or automated
cancellations and modifications are not executing, check these settings first.
| crew_booker_email | string <email> |
| cancellation_lookback_days | integer |
{- "crew_booker_email": "crew-ops@flymx.com",
- "cancellation_lookback_days": 0
}Sets the crew booker account and the booking preferences exposed here.
crew_booker_email is required — all engine-driven bookings (hotel
layovers, diff-engine amendments and cancellations) are placed under this
account. Manual flight commits via POST /bookings/request are booked
under the bearer token making the call.
Omit a field to keep its current value. The automation execution levers (cancellation / modification / swap / deadhead-flight execution, NOC stats) are Routespring-managed and not set through this API.
| crew_booker_email required | string <email> The Routespring account under which all engine-driven hotel bookings,
amendments, and cancellations are placed. Manual flight commits via
|
| cancellation_lookback_days | integer Days before the processing date a checkout is still eligible for cancellation. Default 0. |
| crew_booker_email | string <email> |
| cancellation_lookback_days | integer |
{- "crew_booker_email": "crew-ops@flymx.com",
- "cancellation_lookback_days": 0
}{- "crew_booker_email": "crew-ops@flymx.com",
- "cancellation_lookback_days": 0
}Returns the immutable snapshot of the named config at the given
version. Use this when a booking's config_version_used references
a non-current version and you need to know exactly which rules
applied.
The response shape depends on config_type: hotels returns a
HotelConfig, flights returns a FlightConfig, etc.
Note: only version = v1 resolves; any other version
returns 404.
| config_type required | string Enum: "hotels" "flights" "flight-rules" |
| version required | string Example: v-2026-05-15-001 |
required | Array of objects (HotelBaseConfig) |
| version | string |
| active | boolean |
| updated_at | string <date-time> |
{- "bases": [
- {
- "base_station": "SLC",
- "hotels": [
- {
- "hotel_config_id": 0,
- "property_id": "string",
- "chain": "HILTON",
- "contract_rate": {
- "amount": 145,
- "currency": "USD"
}, - "rate_periods": [
- {
- "rate": {
- "amount": 145,
- "currency": "USD"
}, - "effective_from": "2019-08-24",
- "effective_to": "2019-08-24"
}
], - "policy": "FLEXIBLE",
- "standard_check_in_time": "15:00",
- "standard_check_out_time": "11:00",
- "guaranteed_early_check_in_time": "10:00",
- "guaranteed_late_check_out_time": "14:00",
- "flex_window_minutes": 120,
- "buffer_minutes": 60
}
]
}
], - "version": "v-2026-05-15-001",
- "active": true,
- "updated_at": "2019-08-24T14:15:22Z"
}Bookings are denormalized with all anchor keys, so any combination of the query parameters below returns the same booking record set.
At least one filter is required.
| schedule_id | string |
| employee_id | string Airline's HR employee id — the crew anchor. |
| pairing_number | string |
| flight_number | string |
| irop_ref | string Airline IROP correlation id. Returns every booking tied to a
single IROP — useful for after-action cost reporting and
audit. Set on bookings created via |
| source | string Enum: "SCHEDULE_DIFF" "AIRLINE_INJECTED" "IROP" How the booking originated. Filter for |
| type | string Enum: "HOTEL" "FLIGHT" |
| status | string (BookingStatusEnum) Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW" Lifecycle of a single booking:
Bookings created via |
| from_date | string <date> |
| to_date | string <date> |
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (Booking) | |
| next_cursor | string or null |
{- "bookings": [
- {
- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}Returns paginated flight options for a given origin/destination/date. Use this when you want to manually select a specific flight rather than letting Routespring auto-book via its built-in logic.
Default behavior (no call needed): Routespring auto-selects and
books the best available flight based on your booking configuration
(preferred airlines, cabin, auto_book_max_price, etc.). You
only need this endpoint if you want a human to review and pick.
Typical IROP flow:
flight_option_id to
PATCH /bookings/{booking_id} or to POST /bookings/request
to lock in that specific flight.Results are sourced from the same supplier pool Routespring uses for auto-booking. Prices and availability are live at query time.
| from_airport | string IATA departure airport code. Required unless |
| to_airport | string IATA arrival airport code. Required unless |
| date | string <date> Desired travel date (local at origin). Required unless |
| arrive_by | string <date-time> Latest acceptable arrival time (UTC). Filters out options that arrive too late for the crew's next duty period start. |
| action_id | string If provided, the route and date ( |
| employee_id | string Airline HR employee id. Accepted but not currently applied to result filtering. |
| limit | integer [ 1 .. 50 ] Default: 10 Results per page. Defaults to 10. |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (FlightOption) | |
| next_cursor | string or null |
| total_results | integer Total matching options across all pages (approximate). |
{- "options": [
- {
- "flight_option_id": "of_01HN2X9N1Q4ZJ8P7AKDQRT4F9A",
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "SLC",
- "to_airport": "JFK",
- "dep_time": "2026-05-16T06:00:00Z",
- "arr_time": "2026-05-16T13:30:00Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "price": {
- "amount": 342,
- "currency": "USD"
}, - "stops": 0,
- "available_seats": 4,
- "preferred_carrier": true
}, - {
- "flight_option_id": "of_01HN2X9N1Q4ZJ8P7AKDQRT4F9B",
- "airline_code": "UA",
- "flight_number": "UA887",
- "from_airport": "SLC",
- "to_airport": "JFK",
- "dep_time": "2026-05-16T08:15:00Z",
- "arr_time": "2026-05-16T15:55:00Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Economy Basic",
- "price": {
- "amount": 289,
- "currency": "USD"
}, - "stops": 1,
- "available_seats": 2,
- "preferred_carrier": false
}
], - "next_cursor": "eyJwYWdlIjoyfQ",
- "total_results": 23
}"I've already decided what to book — just book it." Pairs with
GET /flight-options: airline searches, picks an option, posts
the flight_option_id here.
Execution (v1). A HOTEL request is booked for real,
asynchronously, through the same engine the schedule flow uses
(duplicate check → supplier selection in priority order → rate from
config). The call returns 202; poll GET /bookings/{booking_id}/status
for the outcome. A FLIGHT commit that carries a flight_option_id is
booked for real through Routespring's internal Flights API — it tickets and yields a PNR,
ticket number, and trip-session id; poll GET /bookings/{booking_id}/status,
then read those off the flight block via GET /bookings/{booking_id}.
A FLIGHT commit that omits flight_option_id and instead carries a
route + date (from_airport + to_airport + depart_date) auto-selects
and books the best matching flight — the same way a hotel commit
auto-selects when property_id is omitted (see BookingRequestFlight).
(Deadhead positioning flights derived from POST /schedules are booked
automatically by the engine and don't need this call at all.)
When to use vs alternatives:
POST /action-items instead.PATCH /bookings/{booking_id}.POST /schedules.Typical IROP flow end-to-end:
GET /flight-options?from_airport=EWR&to_airport=SLC&arrive_by=...&employee_id=...flight_option_id here with the anchors and a
classification (IROP_NEW_DEADHEAD, IROP_REROUTE, …).Bookings created here default to manual_override: true so the
eventual catch-up schedule submission's diff engine does not
revert this booking. Set manual_override: false to release
immediately to auto-management.
Idempotency (optional). Send an Idempotency-Key header to make
retries safe: a repeated key for the same airline returns the booking
the first call created instead of committing a second time.
For HOTEL bookings (where there is no flight-options analogue),
the airline supplies the city + check_in_date + check_out_date
plus an optional property_id to pin a specific hotel from its
preferred-hotels config. If property_id is omitted Routespring
auto-selects per priority order — useful for hotel IROPs where
the airline just needs any preferred hotel locked in fast.
| Idempotency-Key | string Optional. A repeated key for the same airline replays the original booking instead of committing a second time. |
| type required | string Enum: "HOTEL" "FLIGHT" |
| classification required | string Airline-supplied classification — same vocabulary as
|
| source required | string Default: "AIRLINE_INJECTED" Enum: "AIRLINE_INJECTED" "IROP" Value: "IROP" |
| irop_ref required | string Required when |
required | object (AnchorsInput) Airline-meaningful identifiers for an injected action item or
booking request. Note |
required | object (BookingRequestFlight) Two ways to commit a positioning flight, mirroring
The crew member the ticket is for is taken from |
object (BookingRequestHotel) | |
| manual_override | boolean Default: true Defaults to |
| notes | string |
{- "type": "FLIGHT",
- "classification": "IROP_NEW_DEADHEAD",
- "source": "IROP",
- "irop_ref": "IROP-2026-05-18-EWR-001",
- "anchors": {
- "traveller_email": "jordan.reyes@flymx.com",
- "employee_id": "EMP-12047",
- "pairing_number": "P-23845"
}, - "flight": {
- "flight_option_id": "of_01HN2X9N1Q4ZJ8P7AKDQRT4F9A"
}
}{- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| booking_id required | string Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V |
| booking_id | string |
| type | string Enum: "HOTEL" "FLIGHT" Determines which detail sub-object is populated: a
|
| status | string (BookingStatusEnum) Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW" Lifecycle of a single booking:
Bookings created via |
| source | string Default: "SCHEDULE_DIFF" Enum: "SCHEDULE_DIFF" "AIRLINE_INJECTED" "IROP" How this booking originated.
|
object | |
object (Anchors) Airline-meaningful identifiers that the booking is denormalized
against — retrieval by any one of these is a single indexed lookup.
All identifiers come from the airline's roster system; On cancellations (bookings and action items), | |
| config_version_used | string The config version in force when this booking was made.
In v1 this is always |
object (Money) For HOTEL bookings: the rate per the airline's contract at the
time of booking (resolved from | |
object (Money) The rate actually charged by the supplier. May differ from
| |
| error_message | string Populated when |
| cancellation_reason | string or null The reason recorded when the booking was cancelled (the |
| retry_count | integer How many times this booking has been retried. Increments on each manual or scheduled retry attempt. Zero on the original attempt. |
| max_retries | integer Maximum number of automatic retries before the booking is considered terminally failed. Configurable per-company. |
| manual_override | boolean When Why this exists: prevents the diff engine from silently
reverting a deliberate manual change. The trade-off: while
|
object (HotelBookingDetail) | |
object (FlightBookingDetail) | |
| created_at | string <date-time> |
| updated_at | string <date-time> |
{- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Modify an existing booking. Routespring performs the supplier change
and returns 202 with the booking record reflecting the outcome
(MODIFIED on a successful amend, or NEEDS_REVIEW if the supplier
rejected it).
How the change is applied. A hotel date-window change
(check-in / check-out) is applied as a true in-place supplier amend —
the booking_id and supplier confirmation are preserved and the
booking ends in MODIFIED. A hotel property swap or any
flight change is applied as a cancel-and-rebook: the original
booking moves to CANCELLED and a new CONFIRMED booking is created.
Company levers (Routespring-managed, not set via this API in v1).
An explicit PATCH always performs the requested amend — it is a
deliberate operator action and is not gated by the per-company
booking-modification execution lever (that lever governs only the
automatic schedule-diff path). The strict-price-match lever
still controls how strictly the amended rate must match the
contracted rate (a mismatch fails the amend in strict mode, or is
flagged in loose mode). These are configured by Routespring during
onboarding; there is no self-serve config surface for them. Valid
field combinations: send hotel
for HOTEL bookings and flight for FLIGHT bookings (matching the
booking's type); manual_override may be sent alone or alongside.
Side effect — manual_override becomes true by default
after a successful PATCH. Once set, the diff engine will no
longer touch this booking on subsequent schedule submissions.
To explicitly release the booking back to automatic management,
PATCH with { "manual_override": false } (alone or alongside
other changes).
| booking_id required | string Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V |
| reason | string |
| manual_override | boolean Optional. Defaults to |
object | |
object Either provide |
{- "reason": "PAIRING_EXTENDED",
- "hotel": {
- "check_out_date": "2026-05-19"
}
}{- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Cancel a booking. Routespring resolves the underlying supplier
transaction and issues the cancellation, then returns 202 with the
booking reflecting the outcome: CANCELLED on success, or
NEEDS_REVIEW if the supplier rejected the cancellation (the live
ticket/room is still out there and an operator must reconcile it).
Re-issuing DELETE on a NEEDS_REVIEW booking retries the cancel;
a booking already CANCELLED is idempotent (no second supplier call).
Side effect — manual_override becomes true. A cancelled
booking is locked from the diff engine regardless of the supplier
outcome; if the underlying pairing later reappears, Routespring will
NOT auto-create a replacement. The airline must explicitly re-issue a
new booking (e.g. by re-uploading the schedule with that pairing
re-included).
| booking_id required | string Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V |
| reason | string Free-text cancellation reason (recorded in audit log) |
{- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| booking_id required | string Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V |
| booking_id | string |
| current_status | string (BookingStatusEnum) Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW" Lifecycle of a single booking:
Bookings created via |
Array of objects |
{- "booking_id": "string",
- "current_status": "PENDING",
- "history": [
- {
- "status": "PENDING",
- "changed_at": "2019-08-24T14:15:22Z",
- "reason": "string"
}
]
}Re-attempt supplier booking for a single booking. The booking must
be in FAILED state — once a retry starts the booking moves to
PENDING, so a re-POST while a retry is already in progress returns
409. Each accepted retry increments retry_count on the booking.
The supplier call runs asynchronously; the response returns
immediately with the booking in PENDING state.
| booking_id required | string Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V |
{- "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "type": "HOTEL",
- "status": "PENDING",
- "source": "SCHEDULE_DIFF",
- "supplier": {
- "name": "Hilton",
- "confirmation_number": "HIL-89A3X7"
}, - "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}, - "config_version_used": "string",
- "contracted_rate": {
- "amount": 145,
- "currency": "USD"
}, - "booked_rate": {
- "amount": 145,
- "currency": "USD"
}, - "error_message": "string",
- "cancellation_reason": "string",
- "retry_count": 1,
- "max_retries": 3,
- "manual_override": true,
- "hotel": {
- "property_id": "string",
- "property_name": "string",
- "city": "SLC",
- "check_in_date": "2019-08-24",
- "check_out_date": "2019-08-24",
- "room_type": "string"
}, - "flight": {
- "airline_code": "DL",
- "flight_number": "DL1234",
- "from_airport": "string",
- "to_airport": "string",
- "dep_time": "2019-08-24T14:15:22Z",
- "arr_time": "2019-08-24T14:15:22Z",
- "cabin_class": "ECONOMY",
- "fare_name": "Main Cabin",
- "pnr": "ABC123",
- "ticket_id": "0167891234567",
- "transaction_id": "txn_4f9c2a",
- "trip_session_id": "1284417"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns bookings that have failed and are scheduled for an automatic retry attempt. Each entry shows the next scheduled attempt time and the prior error.
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (RetryQueueItem) | |
| next_cursor | string or null |
{- "items": [
- {
- "booking_id": "string",
- "retry_count": 0,
- "max_retries": 0,
- "last_error_message": "string",
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string",
- "irop_ref": "string"
}
}
], - "next_cursor": "string"
}Returns recent retry jobs plus their status (RUNNING / COMPLETED / FAILED) and aggregate counts.
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (RetryJob) | |
| next_cursor | string or null |
{- "retry_jobs": [
- {
- "retry_job_id": "rtj_01HMXX9N1Q4ZJ8P7AKDQRT4F9M",
- "status": "QUEUED",
- "total": 0,
- "succeeded": 0,
- "failed": 0,
- "in_flight": 0,
- "pending": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "reason": "string",
- "booking_ids": [
- "string"
], - "schedule_id": "string",
- "triggered_by": "string"
}
], - "next_cursor": "string"
}| retry_job_id required | string Example: rtj_01HMXX9N1Q4ZJ8P7AKDQRT4F9M |
| retry_job_id | string |
| status | string Enum: "QUEUED" "RUNNING" "COMPLETED" "FAILED" |
| total | integer Total bookings included in this job |
| succeeded | integer |
| failed | integer |
| in_flight | integer Bookings currently being attempted |
| pending | integer Bookings still queued |
| started_at | string <date-time> |
| completed_at | string or null <date-time> |
| reason | string |
| booking_ids | Array of strings The booking IDs included in this retry job. |
| schedule_id | string or null Set when the job was created from a filter scoped to a single schedule. |
| triggered_by | string Who initiated the retry — an agent's email address for
UI/API-driven retries, or a system token (e.g. |
{- "retry_job_id": "rtj_01HMXX9N1Q4ZJ8P7AKDQRT4F9M",
- "status": "QUEUED",
- "total": 0,
- "succeeded": 0,
- "failed": 0,
- "in_flight": 0,
- "pending": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "reason": "string",
- "booking_ids": [
- "string"
], - "schedule_id": "string",
- "triggered_by": "string"
}An action item is the engine's decision record — what Routespring determined needs to happen after comparing your roster against existing bookings. Before any supplier call is made, the engine creates an action item for each required change: book a new hotel, cancel a layover, extend a stay, position a crew member on a deadhead flight.
Action items are created two ways:
POST /crew/action-items to tell Routespring what's needed and let the
engine handle supplier selection.Every action item has a disposition:
AUTO_BOOKED — the engine booked it automatically. A booking record exists.AUTO_CANCELLED — a subtype: CANCEL action the engine cancelled
automatically with the supplier; no human action needed.PENDING_REVIEW — human action required. Check recommended_action for
what to do next.FAILED — the supplier call was attempted but failed. Use the retry
endpoints to recover.Both paths produce the same downstream artifact — action items flow through the same booking engine, apply the same configuration, and produce the same booking records.
Returns the action items Routespring derived from this schedule submission. Each item represents one booking decision — what the engine determined needed to happen and whether it acted automatically or held it for review.
Call this after polling processing-status until poll_recommended
is false. Filter by disposition: PENDING_REVIEW to find items
requiring human action, or by type: FLIGHT to see all deadhead
positioning needs.
| schedule_id required | string Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M |
| type | string Enum: "HOTEL" "FLIGHT" Filter by booking type — |
| subtype | string Enum: "NEW_BOOKING" "MODIFY" "CANCEL" Filter by the nature of the change — |
| priority | string Enum: "P1" "P2" "P3" "P4" Filter by urgency — |
| disposition | string Enum: "AUTO_BOOKED" "AUTO_CANCELLED" "PENDING_REVIEW" "FAILED" Filter by outcome — |
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (ActionItem) | |
| next_cursor | string or null |
{- "action_items": [
- {
- "action_id": "act_01HMWX9TG3FJ8K9V2N1QS7HRPM",
- "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
- "source": "SCHEDULE_DIFF",
- "type": "HOTEL",
- "subtype": "NEW_BOOKING",
- "classification": "NEW_LAYOVER",
- "priority": "P3",
- "disposition": "AUTO_BOOKED",
- "auto_eligible": true,
- "anchors": {
- "employee_id": "EMP-1042",
- "pairing_number": "MX-20260527-4821",
- "duty_date": "2026-05-27"
}, - "source_rows": {
- "day1_row": null,
- "day2_row": 3
}, - "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
- "created_at": "2026-05-27T08:00:45Z"
}, - {
- "action_id": "act_01HMWX9TG3FJ8K9V2N1QS8HRPN",
- "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
- "source": "SCHEDULE_DIFF",
- "type": "FLIGHT",
- "subtype": "NEW_BOOKING",
- "classification": "NEW_DEADHEAD",
- "priority": "P3",
- "disposition": "AUTO_BOOKED",
- "auto_eligible": true,
- "anchors": {
- "employee_id": "EMP-1042",
- "pairing_number": "MX-20260527-4821",
- "duty_date": "2026-05-28"
}, - "source_rows": {
- "day1_row": null,
- "day2_row": 7
}, - "booking_id": "bkg_01HMWXA6F3PK9V2N1QS8HRPN",
- "flight_options_url": "/flight-options?from_airport=TUL&to_airport=DAL&date=2026-05-28&employee_id=EMP-1042",
- "created_at": "2026-05-27T08:00:46Z"
}
], - "next_cursor": null
}The airline already knows what booking is required and asks Routespring to run it through the booking engine — searching the supplier pool, applying the active configuration, and either auto-booking or flagging for review per the same rules that govern diff-derived action items.
Primary use case — IROPs. When operations diverges from the
published roster (weather diversion, sick crew, cascading delay),
the airline computes the new booking requirement before your crew
management system catches up. Calling this endpoint
with source: IROP and an irop_ref correlates every action
item, booking, and audit event for that IROP for after-action
analysis.
When to use vs alternatives:
POST /schedules. Routespring
computes the action items.GET /flight-options)
and wants to commit it: POST /bookings/request instead — that
path skips the search step and goes straight to the supplier.Returns the created ActionItem immediately (202).
Execution (v1). Both HOTEL and FLIGHT injections are
auto-booked asynchronously through the same engine the schedule flow
uses (supplier search + selection + rate/fare from config); poll
GET /bookings/{booking_id}/status for the outcome. POST /action-items
takes no flight_option_id — the engine searches and selects the
positioning flight for you. (If you have already chosen a specific
flight via GET /flight-options and want to commit that exact one,
use POST /bookings/request instead.) Resulting Booking.manual_override
defaults to true so a subsequent schedule submission's diff engine does
not revert the injected booking.
Idempotency (optional). Send an Idempotency-Key header to make
retries safe: a repeated key for the same airline replays the original
action item instead of injecting (and booking) a second time.
| Idempotency-Key | string Optional. A repeated key for the same airline replays the original action item instead of injecting a second time. |
| type required | string Enum: "HOTEL" "FLIGHT" Whether this is a hotel layover booking or a deadhead flight
positioning requirement. Must match the sub-object provided —
|
| classification required | string Airline-supplied classification. Uses the same vocabulary as
|
| priority | string Default: "P2" Enum: "P1" "P2" "P3" "P4" Drives queue ordering. Default |
| source required | string Default: "AIRLINE_INJECTED" Enum: "AIRLINE_INJECTED" "IROP"
Value: "IROP" |
| irop_ref required | string Airline-side correlation id. Required when |
required | object (AnchorsInput) Airline-meaningful identifiers for an injected action item or
booking request. Note |
required | object (InjectedFlightContext) Flight constraints Routespring searches against. The engine applies
the active flight config (preferred airlines, cabin,
|
object (InjectedHotelContext) | |
| manual_override | boolean Default: true Defaults to |
| notes | string Free-text context for this injection — reason for the IROP, operational notes for the travel manager. Appears on the resulting action item. |
{- "type": "FLIGHT",
- "classification": "IROP_NEW_DEADHEAD",
- "priority": "P1",
- "source": "IROP",
- "irop_ref": "IROP-2026-05-18-EWR-001",
- "anchors": {
- "traveller_email": "jordan.reyes@flymx.com",
- "employee_id": "EMP-12047",
- "pairing_number": "P-23845"
}, - "flight": {
- "from_airport": "EWR",
- "to_airport": "SLC",
- "depart_date": "2026-05-18",
- "arrive_by": "2026-05-18T14:00:00Z"
}, - "notes": "Original positioning leg cancelled due to ATC ground stop at EWR."
}{- "action_id": "act_01HMWX9TG3FJ8K9V2N1QS7HRPM",
- "schedule_id": "string",
- "source": "SCHEDULE_DIFF",
- "type": "HOTEL",
- "subtype": "NEW_BOOKING",
- "classification": "NEW_LAYOVER",
- "priority": "P1",
- "disposition": "AUTO_BOOKED",
- "auto_eligible": true,
- "recommended_action": "string",
- "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string"
}, - "source_rows": {
- "day1_row": 0,
- "day2_row": 0
}, - "booking_id": "string",
- "flight_options_url": "string",
- "notes": "string",
- "created_at": "2019-08-24T14:15:22Z"
}| schedule_id required | string Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M |
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (AuditEvent) | |
| next_cursor | string or null |
{- "audit_events": [
- {
- "audit_id": "string",
- "schedule_id": "string",
- "category": "DATA_QUALITY",
- "severity": "INFO",
- "reason": "string",
- "anchors": {
- "employee_id": "string",
- "pairing_number": "string",
- "duty_date": "2019-08-24",
- "flight_number": "string"
}, - "created_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}Search Routespring's GDS hotel inventory by any combination
of name, city, and airport_code. At least one filter must be
supplied.
Use this to discover a hotel's property_id before registering it
via PUT /crew/config/hotels. A property_id found in this GDS
inventory is booked via GDS; one that is not is treated as DIRECT.
How the filters compose. name and city each run an
independent LIKE-search against CONCAT(hotel_name, address, city);
airport_code instead searches the hotels located nearby the given
airport (a proximity search around the airport's location). Results
are unioned, deduplicated on property_id, and sorted.
This is a global lookup: results are the same regardless of the calling airline, so the path is not airline-scoped. Only hotels bookable through GDS are returned. A bearer credential is still required.
| name | string >= 2 characters Partial, case-insensitive hotel-name match. Min 2 chars when supplied. |
| city | string >= 2 characters Partial, case-insensitive city-name match. Min 2 chars when supplied. |
| airport_code | string = 3 characters 3-letter IATA airport code (e.g. |
| limit | integer [ 1 .. 200 ] Default: 50 |
| cursor | string Opaque pagination cursor. Echo from previous response. |
Array of objects (HotelSearchResult) | |
| next_cursor | string or null |
| total_results | integer Total hotels matching the supplied filters (name / city / airport_code) across all pages. |
{- "hotels": [
- {
- "property_id": "TVP-JFK-9001",
- "name": "JFK GDS Grand",
- "chain": "HILTON",
- "city": "JFK",
- "address": "144-02 135th Ave, Jamaica, NY"
}
], - "next_cursor": null,
- "total_results": 1
}