Routespring Crew Travel API (1.0.0)

Download OpenAPI specification:

Routespring Engineering: api@routespring.com

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.


What you can do

  • Automate hotel layover bookings — submit a roster and Routespring books, modifies, and cancels hotel stays as your schedule changes.
  • Book deadhead positioning flights — when crew need repositioning, Routespring searches and books on your behalf.
  • Handle IROPs in near real-time — submit out-of-band hotel or flight requirements directly, bypassing the roster cycle.
  • Monitor every booking — track state transitions, retry failures, and export full audit trails per submission.

How it works

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.


Before you start

  • Sandbox — all examples in this documentation run against the sandbox. Get credentials from your Routespring dashboard under Settings → Integrations → REST API.
  • Interactive walkthrough — the fastest way to see the full flow in action without writing any code.
  • Quickstart — a complete curl-based guide covering every step from authentication to booking retrieval.

What must be provisioned for you

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.


DEMO

▶ 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.

Quickstart

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.


0. Get an access token

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/… requires Authorization: Bearer $BEARER. Your airline (tenant) is derived from the token itself — there is no airline id in the URL. The hotel directory at /hotels also requires it. Missing, malformed, or expired bearer → 401 UNAUTHORIZED (refresh and retry).


0.1 Before your first end-to-end — behaviors that will trip you up

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:

    • Re-POSTing a byte-identical roster is a no-op — no new action items.
    • Changing any key field emits a DEADHEAD_CANCELLED for the old leg and a NEW_DEADHEAD for the new one.
    • When testing, use a fresh 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_airportto_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_airportto_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}.


1. One-time airline setup

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).

1.0 Crew booking account + automation flags

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"

1.1 Hotel inventory + contract rates

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. GDS hotels are looked up against the GDS global inventory by property_id; an unknown id returns 422 UNKNOWN_HOTEL_PROPERTY at PUT time. DIRECT hotels are managed by you and not validated against any external system.

1.2 Flight rules (deadhead thresholds)

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 .

1.3 Flight preferences

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 .

1.4 Verify the config landed

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 .

2. Schedule-driven flow (the normal happy path)

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.

2.1 Submit a roster (JSON)

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 /schedules path derives both HOTEL and FLIGHT (deadhead) action items from the same submission. JSON is the only ingestion format — there is no file-upload endpoint.

2.2 Poll processing status

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 BOOKING rather than COMPLETED so you don't stop polling early. Once every flight task is BOOKED / FAILED / CANCELLED, status reports COMPLETED.

2.3 List derived action items

# 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.

2.4 List the resulting bookings

# 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 /bookings with no filter returns 400 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.

2.5 Get a specific booking + its status history

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

2.6 Modify a booking (date change / hotel swap / fare upgrade)

# 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_override becomes true. 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).

2.7 Cancel a booking

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.


3. IROP / airline-injected flow

When ops already knows what they need and the roster system hasn't caught up.

3.1 Inject an IROP hotel requirement (engine still picks the property)

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 .

3.2 Commit a specific hotel

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.

3.3 Search flight options (manual deadhead picker)

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.

3.4 Commit a specific flight from §3.3

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 .

4. Ops & retries

4.1 Hotel directory (GDS global search, airline-independent)

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}'

4.2 Audit events for a schedule

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 .

4.3 Exports

# Action items as CSV
curl -sS -o actions.csv "$BASE/crew/schedules/$SCHEDULE_ID/actions.csv" \
  -H "Authorization: Bearer $BEARER"

4.4 Retry a failed booking

# 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 .

5. Error responses you should handle

The same envelope is used everywhere:

{ "error": { "code": "<UPPER_SNAKE>", "message": "<human>", "details": [...] }, "status": <int> }

5.1 401 UNAUTHORIZED — missing / malformed bearer

curl -sS "$BASE/crew/schedules" | jq .
# => { "error": { "code": "UNAUTHORIZED", ... }, "status": 401 }

5.2 404 NOT_FOUND — unknown id, or cross-tenant access

curl -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.

5.3 409 CONFLICT — duplicate booking on /bookings/request

A 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 }

5.4 Expired flight option on /bookings/request

A 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.

5.5 422 VALIDATION_FAILED — structural validation

curl -sS -X POST "$BASE/crew/schedules" \
  -H "Authorization: Bearer $BEARER" \
  -H "Content-Type: application/json" \
  -d '{ "pairings": [] }' | jq .
# => { "error": { "code": "VALIDATION_FAILED", ... }, "status": 422 }

6. Full setup-and-smoke-test in one block

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}'

Glossary

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.

Crew

  • Crew Member — A pilot or flight attendant assigned to operate or accompany a flight in a working capacity. Maps to CrewMember.
  • Rank — Role / seniority code: CA Captain, FO First Officer, FA Flight Attendant, PU Purser, SO Second Officer. Airline-conventional. Maps to CrewMember.rank.
  • Crew Base / Home Base / Domicile — The airport a crew member is contractually based at; pairings begin and end here, and TAFB is measured against it. The three terms are interchangeable. Maps to CrewMember.home_base (IATA code).
  • Union Code — The crew member's union (e.g. ALPA, APFA, AFA-CWA, APA). Can affect hotel-tier entitlements and per-diem rules at some carriers. Maps to CrewMember.union_code.

Schedule structure

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).

  • Schedule / Roster — The published assignment of work to crew over a planning horizon (typically a month). Maps to what you submit at POST /schedules and the Schedule resource Routespring builds from it.
  • Pairing — The atomic unit of crew scheduling: a sequence of duty periods that originates and terminates at the same domicile, typically 1–4 days, separated by layovers. Maps to Pairing; identified by pairing_number.
  • Duty Period (FDP — Flight Duty Period) — Roughly one working day: begins at sign-in (~60–90 min before the first flight) and ends after the last flight. Numbered sequentially within a pairing. Maps to DutyPeriod (duty_period_num, duty_date, fdp_start_local, fdp_end_local).
  • Leg (also: Segment) — One movement of one aircraft from one airport to another. Numbered within a duty period (leg_sequence). Maps to Leg.

Activity codes

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
  • Deadhead (DH) — Transportation of a crew member as a passenger to position them for an upcoming duty. Deadhead time counts as duty, not rest — it eats into the crew's daily duty budget, which is what makes positioning expensive and worth automating. This is the FLIGHT booking category in this API.

Time / duty / rest

  • Block Time (also: Block Hours) — Ramp-blocks-out to ramp-blocks-in; most pilots' pay clock, always less than the surrounding FDP. Maps to Leg.block_hours.
  • FDP Start / End — The times that bound a duty period; used by the engine to tell on-duty from scheduled rest (hotel check-in timing, rest minimums). Maps to DutyPeriod.fdp_start_local / fdp_end_local.
  • Layover — A scheduled rest period at an out-station between two duty periods of the same pairing; the crew is off duty and entitled to hotel accommodation. Maps to DutyPeriod.layover.
  • TAFB — Time Away From Base — Total wall-clock duration of a pairing, measured from pairing check-in to pairing check-out — including all duty periods, layovers, and deadhead positioning. Used for per-diem and pairing-bid preferences. Maps to Pairing.tafb_hours.

Aircraft

  • Aircraft Type / TailType is the model designator (B777, A350, B737; ICAO/IATA codes). Tail is the unique registration of one airframe (A6-EBA). Maps to Leg.aircraft_type / Leg.aircraft_tail.

Operations

  • IROPS — Irregular Operations — Disruptions to planned schedules caused by weather, mechanical issues, ATC delays, or other unforeseen events. Reach the engine via the airline-injection path and are treated as high-priority (P1/P2) — typically only hours to react.

Booking & supplier

  • GDS — Global Distribution System — The intermediary inventory and booking platform (e.g. Amadeus, Sabre, Travelport) through which Routespring searches and books hotels and flights. GDS hotels are validated against Routespring's inventory at config time — an unknown property_id returns 422. DIRECT hotels bypass GDS and are managed by the airline directly.
  • PNR — Passenger Name Record — The airline reservation record created when a flight is ticketed. Routespring returns the 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.

Conceptual model

Four conceptual domains:

  1. Schedule ingestion — the airline POSTs the complete current state of all open pairings to 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.
  2. Booking configuration — the airline defines per-base hotel preferences, flight preferences, and flight rules. PUTs are idempotent and versioned; every booking snapshots the active config version (see Booking.config_version_used) so historical bookings can be replayed against the exact rules that were in force.
  3. Booking engine — Routespring processes action items asynchronously, applying configuration to decide between auto-booking and human review.
  4. Retrieval & management — the airline queries bookings by any anchor key, and modifies / cancels bookings out-of-band.

Schedule-driven vs. IROP booking

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.

Anchor keys & ID model

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.

Tracking async outcomes

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.

Disposition vs status vs auto-eligibility

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.

Manual bookings & override rules

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.

Action item classifications

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.

Priority

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.

Authentication

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:

  • One-time: register an OAuth2 client (or receive credentials at onboarding) → 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.

Schedules

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.

Submit a crew schedule

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
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 (YYYY-MM-DD). Routespring only reconciles bookings on or after this date against the submission.

schedule_end_date
required
string <date>

Inclusive end of the schedule window this submission covers (YYYY-MM-DD). Must be on or after schedule_start_date. Routespring only reconciles bookings on or before this date.

required
Array of objects (Pairing) non-empty

Responses

Request samples

Content type
application/json
{
  • "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": [
    ]
}

Response samples

Content type
application/json
{
  • "schedule_id": "sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M",
  • "external_ref": "NB-EXPORT-2026-05-15T14:30Z",
  • "processing_status": "QUEUED",
  • "received_at": "2026-05-15T14:32:11Z",
  • "links": {
    }
}

List schedule submissions

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.

Authorizations:
bearerAuth
query Parameters
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 (QUEUED, PROCESSING, BOOKING) or those that failed (FAILED).

limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (ScheduleDetail)
next_cursor
string or null
total_results
integer

Total schedules matching the filter across all pages.

Response samples

Content type
application/json
{
  • "schedules": [
    ],
  • "next_cursor": null,
  • "total_results": 2
}

Get schedule metadata + summary

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.

Authorizations:
bearerAuth
path Parameters
schedule_id
required
string
Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M

Responses

Response Schema: application/json
schedule_id
string
external_ref
string
processing_status
string (ProcessingStatusEnum)
Enum: "QUEUED" "PROCESSING" "BOOKING" "COMPLETED" "FAILED"

Lifecycle of a schedule submission:

  • QUEUED — accepted, waiting for the diff engine to pick up.
  • PROCESSING — diffing + classifying (fast — seconds).
  • BOOKING — diff complete; supplier calls in flight (slower — minutes).
  • COMPLETED — all supplier calls have terminated (success or failure).
  • FAILED — the submission could not be processed at all (e.g. malformed file). Note: COMPLETED does not imply every action succeeded — individual bookings can still be in FAILED state. See summary.action_items_* for the breakdown and the retry endpoints for follow-up.
received_at
string <date-time>
object
started_at
string <date-time>
completed_at
string <date-time>
object (ScheduleSummary)

Response samples

Content type
application/json
{
  • "schedule_id": "string",
  • "external_ref": "string",
  • "processing_status": "QUEUED",
  • "received_at": "2019-08-24T14:15:22Z",
  • "links": {
    },
  • "started_at": "2019-08-24T14:15:22Z",
  • "completed_at": "2019-08-24T14:15:22Z",
  • "summary": {
    }
}

Poll processing status

Authorizations:
bearerAuth
path Parameters
schedule_id
required
string
Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M

Responses

Response Schema: application/json
schedule_id
string
status
string (ProcessingStatusEnum)
Enum: "QUEUED" "PROCESSING" "BOOKING" "COMPLETED" "FAILED"

Lifecycle of a schedule submission:

  • QUEUED — accepted, waiting for the diff engine to pick up.
  • PROCESSING — diffing + classifying (fast — seconds).
  • BOOKING — diff complete; supplier calls in flight (slower — minutes).
  • COMPLETED — all supplier calls have terminated (success or failure).
  • FAILED — the submission could not be processed at all (e.g. malformed file). Note: COMPLETED does not imply every action succeeded — individual bookings can still be in FAILED state. See summary.action_items_* for the breakdown and the retry endpoints for follow-up.
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 BOOKING until this reaches 0, then flips to COMPLETED.

poll_recommended
boolean

Whether the client should keep polling this endpoint. Use this as the loop condition instead of hard-coding status checks.

true — the schedule is still progressing on its own (QUEUED / PROCESSING, or BOOKING with a supplier call in flight); poll again after a short delay.

false — nothing more will change automatically: either a terminal status (COMPLETED / FAILED / PARTIAL), or BOOKING pinned only by items left for manual action (e.g. a booking surfaced as NEEDS_REVIEW for an agent to resolve). In that case the automatic bookings are already done — stop polling and read …/action-items and …/bookings.

object (ScheduleSummary)

Response samples

Content type
application/json
{
  • "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": {
    }
}

Exports

Downloadable artifacts (output workbook, actions CSV, record mapping, summary, source file)

Download action items as CSV

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.

Authorizations:
bearerAuth
path Parameters
schedule_id
required
string
Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M

Responses

Response Schema: text/csv
string

Response samples

Content type
application/json
{
  • "error": {
    },
  • "status": 0
}

Configuration

Booking configuration (hotels, flights, rules)

Get hotel configuration (per-base preferred hotels + contract rates)

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}.

Authorizations:
bearerAuth

Responses

Response Schema: application/json
required
Array of objects (HotelBaseConfig)
version
string
active
boolean
updated_at
string <date-time>

Response samples

Content type
application/json
{
  • "bases": [
    ],
  • "version": "v-2026-05-15-001",
  • "active": true,
  • "updated_at": "2026-05-15T10:32:00Z"
}

Replace hotel configuration (idempotent, creates new version)

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
required
Array of objects (HotelBaseConfig)

Responses

Response Schema: application/json
required
Array of objects (HotelBaseConfig)
version
string
active
boolean
updated_at
string <date-time>

Request samples

Content type
application/json
{
  • "bases": [
    ]
}

Response samples

Content type
application/json
{
  • "bases": [
    ],
  • "version": "v-2026-05-16-001",
  • "active": true,
  • "updated_at": "2026-05-16T09:15:00Z"
}

List hotel change requests

Enable / disable / remove requests raised for your company, newest first. A request changes nothing on the hotel until Routespring approves it.

Authorizations:
bearerAuth
query Parameters
open_only
boolean
Default: true

When false, decided (approved / declined / withdrawn) requests are included.

Responses

Response Schema: application/json
Array of objects (HotelChangeRequest)

Response samples

Content type
application/json
{
  • "change_requests": [
    ]
}

Withdraw an open change request

Withdraws a request that Routespring has not yet decided. Nothing on the hotel changes.

Authorizations:
bearerAuth
path Parameters
request_id
required
string
Example: HMR-3f9a1c22

Responses

Response Schema: application/json
request_id
string
hotel_config_id
integer <int64>
contract_id
integer <int64>
intent
string
Enum: "ENABLE" "DISABLE" "DELETE"

DELETE appears only on historical rows; removal is no longer a reviewed action.

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

Response samples

Content type
application/json
{
  • "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"
}

Request that automated booking be switched on or off

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
Request Body schema: application/json
required
intent
required
string
Enum: "ENABLE" "DISABLE"
  • ENABLE — ask for automated booking to be switched on for the contract.
  • DISABLE — ask for it to be switched off.
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 422 without it, rather than guessing which one to switch.

note
string

Free text for the reviewer.

Responses

Request samples

Content type
application/json
{
  • "intent": "ENABLE",
  • "contract_id": 0,
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

List a hotel's contracts (with their rates)

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>

Responses

Response Schema: application/json
Array of objects (HotelContract)

Response samples

Content type
application/json
{
  • "contracts": [
    ]
}

Create a contract on a hotel

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
Request Body schema: application/json
required
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

Responses

Response Schema: application/json
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 enabled: a correctly priced contract reads true while still switched off, which is exactly the state you request ENABLE from.

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>

Request samples

Content type
application/json
{
  • "effective_from": "2026-09-01",
  • "effective_to": "2027-08-31",
  • "contract_number": "BRZ-DAL-2026-14",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "has_document": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update contract details

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
contract_id
required
integer <int64>
Request Body schema: application/json
required
effective_from
string <date>
effective_to
string <date>
contract_number
string
notes
string

Responses

Response Schema: application/json
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 enabled: a correctly priced contract reads true while still switched off, which is exactly the state you request ENABLE from.

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>

Request samples

Content type
application/json
{
  • "effective_from": "2019-08-24",
  • "effective_to": "2019-08-24",
  • "contract_number": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "has_document": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Delete a contract

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
contract_id
required
integer <int64>

Responses

Response samples

Content type
application/json
{
  • "error": {
    },
  • "status": 0
}

List a contract's rate line items

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
contract_id
required
integer <int64>

Responses

Response Schema: application/json
Array of objects (ContractRate)
fully_covered
boolean

Whether these rates tile the contract window exactly.

Response samples

Content type
application/json
{
  • "rates": [
    ],
  • "fully_covered": true
}

Replace a contract's rate line items

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.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
contract_id
required
integer <int64>
Request Body schema: application/json
required
required
Array of objects non-empty

Responses

Response Schema: application/json
Array of objects (ContractRate)
fully_covered
boolean

Request samples

Content type
application/json
{
  • "rates": [
    ]
}

Response samples

Content type
application/json
{
  • "rates": [
    ],
  • "fully_covered": true
}

Remove all of a contract's rate line items

Clears the whole rate set. Refused while the hotel is live for crew booking, since it would silently make every future stay unpriceable.

Authorizations:
bearerAuth
path Parameters
hotel_config_id
required
integer <int64>
contract_id
required
integer <int64>

Responses

Response samples

Content type
application/json
{
  • "error": {
    },
  • "status": 0
}

Get flight configuration (preferred / blacklisted airlines, cabin)

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.

Authorizations:
bearerAuth

Responses

Response Schema: application/json
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 cabin_class field on POST /bookings/request.

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.

version
string
updated_at
string <date-time>

Response samples

Content type
application/json
{
  • "preferred_airlines": [
    ],
  • "blacklisted_airlines": [
    ],
  • "preferred_cabin": "ECONOMY",
  • "same_carrier_preference": true,
  • "version": "v-2026-05-15-001",
  • "updated_at": "2026-05-15T10:36:00Z"
}

Replace flight configuration

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
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 cabin_class field on POST /bookings/request.

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.

Responses

Response Schema: application/json
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 cabin_class field on POST /bookings/request.

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.

version
string
updated_at
string <date-time>

Request samples

Content type
application/json
{
  • "preferred_airlines": [
    ],
  • "blacklisted_airlines": [
    ],
  • "preferred_cabin": "ECONOMY",
  • "same_carrier_preference": true
}

Response samples

Content type
application/json
{
  • "preferred_airlines": [
    ],
  • "blacklisted_airlines": [
    ],
  • "preferred_cabin": "ECONOMY",
  • "same_carrier_preference": true,
  • "version": "v-2026-05-16-001",
  • "updated_at": "2026-05-16T09:20:00Z"
}

Get flight rules (auto-book price ceiling)

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.

Authorizations:
bearerAuth

Responses

Response Schema: application/json
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 NEEDS_REVIEW for travel manager approval. Omit to apply no price ceiling.

version
string

Identifier of this config version, referenced by bookings via config_version_used.

updated_at
string <date-time>

Timestamp when this version was created.

Response samples

Content type
application/json
{
  • "auto_book_max_price": {
    },
  • "version": "string",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Replace flight rules

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
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 NEEDS_REVIEW for travel manager approval. Omit to apply no price ceiling.

Responses

Response Schema: application/json
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 NEEDS_REVIEW for travel manager approval. Omit to apply no price ceiling.

version
string

Identifier of this config version, referenced by bookings via config_version_used.

updated_at
string <date-time>

Timestamp when this version was created.

Request samples

Content type
application/json
{
  • "auto_book_max_price": {
    }
}

Response samples

Content type
application/json
{
  • "auto_book_max_price": {
    },
  • "version": "v-2026-05-16-001",
  • "updated_at": "2026-05-16T09:22:00Z"
}

Get company crew-automation settings

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.

Authorizations:
bearerAuth

Responses

Response Schema: application/json
crew_booker_email
string <email>
cancellation_lookback_days
integer

Response samples

Content type
application/json
{
  • "crew_booker_email": "crew-ops@flymx.com",
  • "cancellation_lookback_days": 0
}

Set company crew-automation settings

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
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 POST /bookings/request are booked under the bearer token making the call, not this account.

cancellation_lookback_days
integer

Days before the processing date a checkout is still eligible for cancellation. Default 0.

Responses

Response Schema: application/json
crew_booker_email
string <email>
cancellation_lookback_days
integer

Request samples

Content type
application/json
{
  • "crew_booker_email": "crew-ops@flymx.com",
  • "cancellation_lookback_days": 0
}

Response samples

Content type
application/json
{
  • "crew_booker_email": "crew-ops@flymx.com",
  • "cancellation_lookback_days": 0
}

Retrieve a specific historical config version

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.

Authorizations:
bearerAuth
path Parameters
config_type
required
string
Enum: "hotels" "flights" "flight-rules"
version
required
string
Example: v-2026-05-15-001

Responses

Response Schema: application/json
One of
required
Array of objects (HotelBaseConfig)
version
string
active
boolean
updated_at
string <date-time>

Response samples

Content type
application/json
Example
{
  • "bases": [
    ],
  • "version": "v-2026-05-15-001",
  • "active": true,
  • "updated_at": "2019-08-24T14:15:22Z"
}

Bookings

Booking retrieval and lifecycle management

Search bookings by any anchor key

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.

Authorizations:
bearerAuth
query Parameters
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 POST /bookings/request or POST /action-items with an irop_ref.

source
string
Enum: "SCHEDULE_DIFF" "AIRLINE_INJECTED" "IROP"

How the booking originated. Filter for AIRLINE_INJECTED or IROP to find airline-injected bookings.

type
string
Enum: "HOTEL" "FLIGHT"
status
string (BookingStatusEnum)
Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW"

Lifecycle of a single booking:

  • PENDING — supplier call in flight; awaiting confirmation.
  • CONFIRMED — supplier confirmed; PNR / voucher issued.
  • MODIFIED — booking amended in place after confirmation. Still effectively confirmed; the status reflects that an amend has been applied since the original CONFIRMED transition. v1 reaches MODIFIED for hotel date-window changes (check-in / check-out) and requires the airline's hotel-modification feature; property swaps and flight changes are applied as cancel-and-rebook and surface as CANCELLED plus a new CONFIRMED booking rather than MODIFIED.
  • CANCELLED — booking cancelled (by DELETE /bookings/{id}, by retry abandonment after max_retries, or by supplier-side action).
  • FAILED — supplier call returned an error and the booking is not in place. See Booking.error_message and the /retry endpoints. Distinct from NEEDS_REVIEW: a FAILED booking was attempted; a NEEDS_REVIEW booking has not been attempted yet.
  • NEEDS_REVIEW — engine held the booking for human action (no preferred vendor in config, price exceeds auto_book_max_price, etc.). The supplier call has not been made; a travel manager must intervene — typically via PATCH /bookings/{id} after resolving the underlying issue.

Bookings created via POST /bookings/request typically start at PENDING and transition to CONFIRMED (or FAILED). Bookings born from POST /action-items or POST /schedules may also start at NEEDS_REVIEW if the engine's eligibility checks require human disposition.

from_date
string <date>
to_date
string <date>
limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (Booking)
next_cursor
string or null

Response samples

Content type
application/json
{
  • "bookings": [
    ],
  • "next_cursor": "string"
}

Search available flight options for a deadhead leg

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:

  1. Call this endpoint to fetch options (10 per page by default).
  2. Present pages to the travel manager.
  3. POST the chosen 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.

Authorizations:
bearerAuth
query Parameters
from_airport
string

IATA departure airport code. Required unless action_id is supplied.

to_airport
string

IATA arrival airport code. Required unless action_id is supplied.

date
string <date>

Desired travel date (local at origin). Required unless action_id is supplied.

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 (from_airport, to_airport, date, arrive_by) are pre-filled from the named action item, so you may omit those params. An unknown action_id returns 404.

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.

Responses

Response Schema: application/json
Array of objects (FlightOption)
next_cursor
string or null
total_results
integer

Total matching options across all pages (approximate).

Response samples

Content type
application/json
{
  • "options": [
    ],
  • "next_cursor": "eyJwYWdlIjoyfQ",
  • "total_results": 23
}

Create a booking from an airline-supplied selection (IROP commit)

"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:

  • The airline wants Routespring to search + auto-book against its config: use POST /action-items instead.
  • The booking already exists and needs to change: use PATCH /bookings/{booking_id}.
  • Diff-derived (normal flow): just POST /schedules.

Typical IROP flow end-to-end:

  1. Detect IROP in airline ops system.
  2. GET /flight-options?from_airport=EWR&to_airport=SLC&arrive_by=...&employee_id=...
  3. Travel manager picks an option (or your automation does).
  4. POST that 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.

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
string

Optional. A repeated key for the same airline replays the original booking instead of committing a second time.

Request Body schema: application/json
required
One of
One of
type
required
string
Enum: "HOTEL" "FLIGHT"
classification
required
string

Airline-supplied classification — same vocabulary as ActionItem.classification. Recorded on the resulting booking for audit and on the synthesized action item that backs it.

source
required
string
Default: "AIRLINE_INJECTED"
Enum: "AIRLINE_INJECTED" "IROP"
Value: "IROP"
irop_ref
required
string

Required when source: IROP. Correlates this booking with every other artifact for the same IROP — searchable via GET /bookings?irop_ref=....

required
object (AnchorsInput)

Airline-meaningful identifiers for an injected action item or booking request. traveller_email is required so Routespring can resolve the crew member's bookable profile. employee_id and the other anchors are optional but encouraged when known — they flow into the booking's denormalized index so retrieval by any one is a single lookup.

Note pairing_number is recommended even for IROP-driven bookings where the original pairing has been disrupted: keeping the link lets the airline and Routespring see the IROP-injected booking next to the original pairing's other bookings in the same view.

required
object (BookingRequestFlight)

Two ways to commit a positioning flight, mirroring BookingRequestHotel:

  • Pin a specific flight — pass flight_option_id (from a prior GET /flight-options call) to commit exactly that offer.
  • Auto-select — omit flight_option_id and pass from_airport + to_airport + depart_date (optionally depart_after / arrive_by / cabin_class); Routespring searches and books the best matching flight automatically.

The crew member the ticket is for is taken from anchors.traveller_email. (Deadhead positioning flights derived from POST /schedules are booked automatically by the engine and do not need this call.)

object (BookingRequestHotel)
manual_override
boolean
Default: true

Defaults to true — the diff engine will not modify this booking on subsequent schedule submissions, even if the eventual catch-up roster contradicts it. Pass false to release immediately to auto-management.

notes
string

Responses

Request samples

Content type
application/json
Example
{
  • "type": "FLIGHT",
  • "classification": "IROP_NEW_DEADHEAD",
  • "source": "IROP",
  • "irop_ref": "IROP-2026-05-18-EWR-001",
  • "anchors": {
    },
  • "flight": {
    }
}

Response samples

Content type
application/json
{
  • "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
  • "type": "HOTEL",
  • "status": "PENDING",
  • "source": "SCHEDULE_DIFF",
  • "supplier": {
    },
  • "anchors": {
    },
  • "config_version_used": "string",
  • "contracted_rate": {
    },
  • "booked_rate": {
    },
  • "error_message": "string",
  • "cancellation_reason": "string",
  • "retry_count": 1,
  • "max_retries": 3,
  • "manual_override": true,
  • "hotel": {
    },
  • "flight": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get booking detail

Authorizations:
bearerAuth
path Parameters
booking_id
required
string
Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V

Responses

Response Schema: application/json
booking_id
string
type
string
Enum: "HOTEL" "FLIGHT"

Determines which detail sub-object is populated: a HOTEL booking carries hotel (and flight is null); a FLIGHT booking carries flight (and hotel is null).

FLIGHT bookings in v1 originate from both the schedule diff (deadhead positioning — a DH leg booked on its explicit from_airportto_airport, arriving by the duty's fdp_start) and airline injection (POST /bookings/request / POST /action-items).

status
string (BookingStatusEnum)
Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW"

Lifecycle of a single booking:

  • PENDING — supplier call in flight; awaiting confirmation.
  • CONFIRMED — supplier confirmed; PNR / voucher issued.
  • MODIFIED — booking amended in place after confirmation. Still effectively confirmed; the status reflects that an amend has been applied since the original CONFIRMED transition. v1 reaches MODIFIED for hotel date-window changes (check-in / check-out) and requires the airline's hotel-modification feature; property swaps and flight changes are applied as cancel-and-rebook and surface as CANCELLED plus a new CONFIRMED booking rather than MODIFIED.
  • CANCELLED — booking cancelled (by DELETE /bookings/{id}, by retry abandonment after max_retries, or by supplier-side action).
  • FAILED — supplier call returned an error and the booking is not in place. See Booking.error_message and the /retry endpoints. Distinct from NEEDS_REVIEW: a FAILED booking was attempted; a NEEDS_REVIEW booking has not been attempted yet.
  • NEEDS_REVIEW — engine held the booking for human action (no preferred vendor in config, price exceeds auto_book_max_price, etc.). The supplier call has not been made; a travel manager must intervene — typically via PATCH /bookings/{id} after resolving the underlying issue.

Bookings created via POST /bookings/request typically start at PENDING and transition to CONFIRMED (or FAILED). Bookings born from POST /action-items or POST /schedules may also start at NEEDS_REVIEW if the engine's eligibility checks require human disposition.

source
string
Default: "SCHEDULE_DIFF"
Enum: "SCHEDULE_DIFF" "AIRLINE_INJECTED" "IROP"

How this booking originated.

  • SCHEDULE_DIFF — born from a POST /schedules diff-derived action item.
  • AIRLINE_INJECTED — created via POST /bookings/request or POST /action-items (non-IROP).
  • IROP — created via POST /bookings/request or POST /action-items with source: IROP. Bookings with source of AIRLINE_INJECTED or IROP default to manual_override: true.
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; irop_ref is supplied by the airline when injecting an action item or booking request tied to an irregular-operations event. Routespring does not mint its own crew/pairing/duty/leg ids.

On cancellations (bookings and action items), pairing_number may be absent when the cancelled stay cannot be attributed to a specific pairing.

config_version_used
string

The config version in force when this booking was made. In v1 this is always "v1" — only a single config version exists.

object (Money)

For HOTEL bookings: the rate per the airline's contract at the time of booking (resolved from PreferredHotel.contract_rate or the matching rate_periods entry). For FLIGHT bookings: typically null (deadhead flights don't have contracted rates).

object (Money)

The rate actually charged by the supplier. May differ from contracted_rate when a supplier returns a different rate (price discrepancy — surfaces in audit dashboards). For FLIGHT bookings this is the fare paid.

error_message
string

Populated when status: FAILED. Free-text supplier or engine error message. E.g. "Supplier returned 404: No availability", "Card declined (insufficient funds)". Use to decide whether to retry, escalate, or accept failure.

cancellation_reason
string or null

The reason recorded when the booking was cancelled (the reason passed to DELETE /bookings/{booking_id}). Surfaced here for convenience; the full status history remains on GET /bookings/{booking_id}/status.

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 true, this booking is "owned" by manual edits — Routespring's diff engine will NOT touch it on subsequent schedule submissions, even if the upstream pairing changes. Automatically set to true by any successful PATCH /bookings/{id} or DELETE /bookings/{id} call. To release the booking back to automatic management (allowing future schedule submissions to modify or cancel it), PATCH with { "manual_override": false }.

Why this exists: prevents the diff engine from silently reverting a deliberate manual change. The trade-off: while manual_override is true, the booking won't follow the roster anymore — it's the airline's responsibility to keep it in sync or release it back.

object (HotelBookingDetail)
object (FlightBookingDetail)
created_at
string <date-time>
updated_at
string <date-time>

Response samples

Content type
application/json
{
  • "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
  • "type": "HOTEL",
  • "status": "PENDING",
  • "source": "SCHEDULE_DIFF",
  • "supplier": {
    },
  • "anchors": {
    },
  • "config_version_used": "string",
  • "contracted_rate": {
    },
  • "booked_rate": {
    },
  • "error_message": "string",
  • "cancellation_reason": "string",
  • "retry_count": 1,
  • "max_retries": 3,
  • "manual_override": true,
  • "hotel": {
    },
  • "flight": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Modify a booking (date change, hotel swap, fare upgrade)

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).

Authorizations:
bearerAuth
path Parameters
booking_id
required
string
Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V
Request Body schema: application/json
required
reason
string
manual_override
boolean

Optional. Defaults to true on any modification request (so once you patch a booking, the diff engine stops managing it). Pass false explicitly to release the booking back to automatic management — e.g. "I overrode this last week; the issue is resolved, please let auto take over again."

object
object

Either provide flight_option_id (from GET /flight-options) to book a specific flight, or provide desired_arrival_by to let Routespring auto-select. Omit the flight block entirely to use full auto-booking based on booking configuration.

Responses

Request samples

Content type
application/json
Example
{
  • "reason": "PAIRING_EXTENDED",
  • "hotel": {
    }
}

Response samples

Content type
application/json
{
  • "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
  • "type": "HOTEL",
  • "status": "PENDING",
  • "source": "SCHEDULE_DIFF",
  • "supplier": {
    },
  • "anchors": {
    },
  • "config_version_used": "string",
  • "contracted_rate": {
    },
  • "booked_rate": {
    },
  • "error_message": "string",
  • "cancellation_reason": "string",
  • "retry_count": 1,
  • "max_retries": 3,
  • "manual_override": true,
  • "hotel": {
    },
  • "flight": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Cancel a booking

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).

Authorizations:
bearerAuth
path Parameters
booking_id
required
string
Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V
query Parameters
reason
string

Free-text cancellation reason (recorded in audit log)

Responses

Response samples

Content type
application/json
{
  • "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
  • "type": "HOTEL",
  • "status": "PENDING",
  • "source": "SCHEDULE_DIFF",
  • "supplier": {
    },
  • "anchors": {
    },
  • "config_version_used": "string",
  • "contracted_rate": {
    },
  • "booked_rate": {
    },
  • "error_message": "string",
  • "cancellation_reason": "string",
  • "retry_count": 1,
  • "max_retries": 3,
  • "manual_override": true,
  • "hotel": {
    },
  • "flight": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get booking status + state-transition history

Authorizations:
bearerAuth
path Parameters
booking_id
required
string
Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V

Responses

Response Schema: application/json
booking_id
string
current_status
string (BookingStatusEnum)
Enum: "PENDING" "CONFIRMED" "MODIFIED" "CANCELLED" "FAILED" "NEEDS_REVIEW"

Lifecycle of a single booking:

  • PENDING — supplier call in flight; awaiting confirmation.
  • CONFIRMED — supplier confirmed; PNR / voucher issued.
  • MODIFIED — booking amended in place after confirmation. Still effectively confirmed; the status reflects that an amend has been applied since the original CONFIRMED transition. v1 reaches MODIFIED for hotel date-window changes (check-in / check-out) and requires the airline's hotel-modification feature; property swaps and flight changes are applied as cancel-and-rebook and surface as CANCELLED plus a new CONFIRMED booking rather than MODIFIED.
  • CANCELLED — booking cancelled (by DELETE /bookings/{id}, by retry abandonment after max_retries, or by supplier-side action).
  • FAILED — supplier call returned an error and the booking is not in place. See Booking.error_message and the /retry endpoints. Distinct from NEEDS_REVIEW: a FAILED booking was attempted; a NEEDS_REVIEW booking has not been attempted yet.
  • NEEDS_REVIEW — engine held the booking for human action (no preferred vendor in config, price exceeds auto_book_max_price, etc.). The supplier call has not been made; a travel manager must intervene — typically via PATCH /bookings/{id} after resolving the underlying issue.

Bookings created via POST /bookings/request typically start at PENDING and transition to CONFIRMED (or FAILED). Bookings born from POST /action-items or POST /schedules may also start at NEEDS_REVIEW if the engine's eligibility checks require human disposition.

Array of objects

Response samples

Content type
application/json
{
  • "booking_id": "string",
  • "current_status": "PENDING",
  • "history": [
    ]
}

Retries

Manual and bulk retries for failed bookings

Manually retry a failed booking

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.

Authorizations:
bearerAuth
path Parameters
booking_id
required
string
Example: bkg_01HMWXA52N0SQ8FRPDPK7RZ44V

Responses

Response samples

Content type
application/json
{
  • "booking_id": "bkg_01HMWXA52N0SQ8FRPDPK7RZ44V",
  • "type": "HOTEL",
  • "status": "PENDING",
  • "source": "SCHEDULE_DIFF",
  • "supplier": {
    },
  • "anchors": {
    },
  • "config_version_used": "string",
  • "contracted_rate": {
    },
  • "booked_rate": {
    },
  • "error_message": "string",
  • "cancellation_reason": "string",
  • "retry_count": 1,
  • "max_retries": 3,
  • "manual_override": true,
  • "hotel": {
    },
  • "flight": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

List bookings currently queued for retry

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.

Authorizations:
bearerAuth
query Parameters
limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (RetryQueueItem)
next_cursor
string or null

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

List recent retry jobs

Returns recent retry jobs plus their status (RUNNING / COMPLETED / FAILED) and aggregate counts.

Authorizations:
bearerAuth
query Parameters
limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (RetryJob)
next_cursor
string or null

Response samples

Content type
application/json
{
  • "retry_jobs": [
    ],
  • "next_cursor": "string"
}

Get a retry job's status and progress

Authorizations:
bearerAuth
path Parameters
retry_job_id
required
string
Example: rtj_01HMXX9N1Q4ZJ8P7AKDQRT4F9M

Responses

Response Schema: application/json
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. system:auto-retry) for automated ones.

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "schedule_id": "string",
  • "triggered_by": "string"
}

Action Items

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:

  • From a schedule submission — automatically derived when you POST a roster. The engine classifies each change (NEW_LAYOVER, DEADHEAD_CANCELLED, LAYOVER_EXTENDED, etc.) and decides whether to auto-book or hold for review.
  • Injected outside the roster — for IROPs and requirements that exist operationally but are not yet reflected in your roster system. Use 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.

List action items derived from a schedule

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.

Authorizations:
bearerAuth
path Parameters
schedule_id
required
string
Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M
query Parameters
type
string
Enum: "HOTEL" "FLIGHT"

Filter by booking type — HOTEL for layover bookings, FLIGHT for deadhead positioning.

subtype
string
Enum: "NEW_BOOKING" "MODIFY" "CANCEL"

Filter by the nature of the change — NEW_BOOKING, MODIFY, or CANCEL.

priority
string
Enum: "P1" "P2" "P3" "P4"

Filter by urgency — P1 for IROPs requiring immediate action through to P4 for low-urgency backfills.

disposition
string
Enum: "AUTO_BOOKED" "AUTO_CANCELLED" "PENDING_REVIEW" "FAILED"

Filter by outcome — AUTO_BOOKED / AUTO_CANCELLED for items the engine handled automatically, PENDING_REVIEW to find items requiring human action, FAILED to find items needing retry.

limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (ActionItem)
next_cursor
string or null

Response samples

Content type
application/json
{
  • "action_items": [
    ],
  • "next_cursor": null
}

Inject an action item (IROP / out-of-schedule requirement)

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:

  • Diff-derived (normal flow): just POST /schedules. Routespring computes the action items.
  • Airline has not selected a specific flight, wants Routespring to search + auto-book per config: this endpoint.
  • Airline has selected a specific flight (via 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.

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
string

Optional. A repeated key for the same airline replays the original action item instead of injecting a second time.

Request Body schema: application/json
required
One of
One of
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 — HOTEL requires hotel, FLIGHT requires flight.

classification
required
string

Airline-supplied classification. Uses the same vocabulary as ActionItem.classification — common values for injected items: IROP_NEW_DEADHEAD, IROP_REROUTE, IROP_HOTEL_EXTEND, IROP_HOTEL_CANCEL. For non-IROP injections, use the matching NEW_LAYOVER / NEW_DEADHEAD / LAYOVER_EXTENDED value. The engine treats injected classifications the same way as diff-derived ones for disposition rules.

priority
string
Default: "P2"
Enum: "P1" "P2" "P3" "P4"

Drives queue ordering. Default P2. Bump to P1 for IROPs with tight SLAs (~5 min to commit). P3/P4 for low-urgency backfills.

source
required
string
Default: "AIRLINE_INJECTED"
Enum: "AIRLINE_INJECTED" "IROP"

IROP enables IROP-specific telemetry (after-action grouping by irop_ref) and tightens default SLAs. AIRLINE_INJECTED is the general-purpose injection (ad-hoc additions outside both the diff flow and IROPs).

Value: "IROP"
irop_ref
required
string

Airline-side correlation id. Required when source: IROP so all artifacts tied to one IROP can be queried together via GET /bookings?irop_ref=....

required
object (AnchorsInput)

Airline-meaningful identifiers for an injected action item or booking request. traveller_email is required so Routespring can resolve the crew member's bookable profile. employee_id and the other anchors are optional but encouraged when known — they flow into the booking's denormalized index so retrieval by any one is a single lookup.

Note pairing_number is recommended even for IROP-driven bookings where the original pairing has been disrupted: keeping the link lets the airline and Routespring see the IROP-injected booking next to the original pairing's other bookings in the same view.

required
object (InjectedFlightContext)

Flight constraints Routespring searches against. The engine applies the active flight config (preferred airlines, cabin, auto_book_max_price, etc.) to pick a match — or flags PENDING_REVIEW when no match satisfies the config.

object (InjectedHotelContext)
manual_override
boolean
Default: true

Defaults to true — the diff engine will not modify the resulting booking on subsequent schedule submissions. Pass false to release the booking to auto-management immediately on creation.

notes
string

Free-text context for this injection — reason for the IROP, operational notes for the travel manager. Appears on the resulting action item.

Responses

Request samples

Content type
application/json
Example
{
  • "type": "FLIGHT",
  • "classification": "IROP_NEW_DEADHEAD",
  • "priority": "P1",
  • "source": "IROP",
  • "irop_ref": "IROP-2026-05-18-EWR-001",
  • "anchors": {
    },
  • "flight": {
    },
  • "notes": "Original positioning leg cancelled due to ATC ground stop at EWR."
}

Response samples

Content type
application/json
{
  • "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": {
    },
  • "source_rows": {
    },
  • "booking_id": "string",
  • "flight_options_url": "string",
  • "notes": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Audit

Data quality audit events

List data-quality audit events

Authorizations:
bearerAuth
path Parameters
schedule_id
required
string
Example: sch_01HMWX9N1Q4ZJ8P7AKDQRT4F9M
query Parameters
limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
Array of objects (AuditEvent)
next_cursor
string or null

Response samples

Content type
application/json
{
  • "audit_events": [
    ],
  • "next_cursor": "string"
}

Hotels

Global lookup of Routespring's GDS hotel inventory

Search the GDS hotel inventory by name, city, or airport code

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.

Authorizations:
bearerAuth
query Parameters
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. JFK, LAX, LHR). Searches the hotels located nearby the given airport.

limit
integer [ 1 .. 200 ]
Default: 50
cursor
string

Opaque pagination cursor. Echo from previous response.

Responses

Response Schema: application/json
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.

Response samples

Content type
application/json
{
  • "hotels": [
    ],
  • "next_cursor": null,
  • "total_results": 1
}