Routespring Logo
Request Demo

Tutorial · Scheduled operations

Turn a crew roster into controlled bookings.

A roster publish should start the booking workflow, not a new spreadsheet queue. This tutorial shows how to submit the current schedule, let Routespring derive hotel and deadhead changes, and return only exceptions to the airline team.

Updated Current v1 tutorial

Implementation summary

Trigger
A full roster publish or revised schedule window from the airline crew system.
Current v1 input
POST /crew/schedules with pairings, crew, duty periods, legs, and required layovers.
Human boundary
PENDING_REVIEW, NEEDS_REVIEW, and FAILED outcomes go to an owned exception queue.
Result
Traceable hotel and deadhead actions, bookings, changes, cancellations, rates, and final states.

The real-life scenario

Crew Scheduling publishes tomorrow's roster. One JFK-based crew member operates to ORD and requires an overnight room. The integration submits the roster window once. Routespring identifies the layover, applies the airline's preferred-hotel and price rules, creates the booking, and reports whether it was handled automatically or needs review.

Define the operating contract first

Source of truth

Choose the roster or crew scheduling system that owns the complete current state for each submitted date window.

Airline anchors

Preserve employee_id, pairing_number, duty_date, and flight_number so every action can be traced to the operation.

Policy boundary

Configure preferred hotels, contract rates, flight preferences, cabin, and auto-book price limits before sending live demand.

Exception owner

Assign a team and response path for PENDING_REVIEW, NEEDS_REVIEW, and FAILED outcomes before production launch.

Each submission is a complete snapshot inside schedule_start_date and schedule_end_date. Do not send a hand-authored list of changes. Routespring computes the diff against the prior snapshot within that window.

1. Submit the current schedule window

The example contains one crew member and one required ORD layover. Production payloads can carry many pairings, crew members, duty periods, hotel layovers, and deadhead legs.

Terminal
SCHEDULE=$(curl -sS -X POST "$BASE/crew/schedules" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "OPS-20260725-001",
    "as_of": "2026-07-25T12:00:00Z",
    "schedule_start_date": "2026-07-25",
    "schedule_end_date": "2026-07-26",
    "pairings": [{
      "pairing_number": "PAIR-001",
      "origin_base": "JFK",
      "terminus_base": "JFK",
      "crew": [{
        "employee_id": "EMP-1042",
        "name": "Jordan Reyes",
        "rank": "CA",
        "home_base": "JFK",
        "traveller_email": "jreyes@example-air.com"
      }],
      "duty_periods": [{
        "duty_period_num": 1,
        "duty_date": "2026-07-25",
        "fdp_start_local": "2026-07-25T08:00:00-04:00",
        "fdp_end_local": "2026-07-25T18:00:00-05:00",
        "legs": [{
          "leg_sequence": 1,
          "activity_code": "FLT",
          "flight_number": "MX401",
          "from_airport": "JFK",
          "to_airport": "ORD",
          "dep_time_local": "2026-07-25T09:00:00-04:00",
          "arr_time_local": "2026-07-25T10:30:00-05:00"
        }],
        "layover": {
          "station": "ORD",
          "booking_required": true,
          "checkin_time_local": "2026-07-25T11:00:00-05:00",
          "checkout_time_local": "2026-07-26T07:00:00-05:00"
        }
      }]
    }]
  }')

export SCHEDULE_ID=$(echo "$SCHEDULE" | jq -r .schedule_id)
echo "$SCHEDULE" | jq .

2. Wait for automatic work to stop

Schedule processing and supplier booking are asynchronous. Use poll_recommended as the loop condition. A schedule can stop with items held for people even when its top-level processing record is no longer changing.

Terminal
while true; do
  STATUS=$(curl -sS "$BASE/crew/schedules/$SCHEDULE_ID/processing-status" \
    -H "Authorization: Bearer $ACCESS_TOKEN")

  echo "$STATUS" | jq '{status, poll_recommended, summary}'

  if [ "$(echo "$STATUS" | jq -r .poll_recommended)" = "false" ]; then
    break
  fi

  sleep 3
done
COMPLETED means every supplier call has terminated. It does not mean every individual booking succeeded. Always inspect action dispositions and booking states.

3. Separate automation from exception work

Build two views: what the engine completed and what the operating team must decide. This prevents routine volume from hiding the cases where inventory, policy, price, or a supplier call blocked the booking.

Terminal
# Work Routespring completed automatically.
curl -sS -G "$BASE/crew/schedules/$SCHEDULE_ID/action-items" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode "disposition=AUTO_BOOKED" | jq .

# The operating queue for people.
curl -sS -G "$BASE/crew/schedules/$SCHEDULE_ID/action-items" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode "disposition=PENDING_REVIEW" | jq .

# Confirmed, failed, or review-state bookings tied to the submission.
curl -sS -G "$BASE/crew/bookings" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode "schedule_id=$SCHEDULE_ID" | jq .

4. Handle the next roster publish safely

  • Send the new complete state for the same date window with a new external_ref and as_of timestamp.
  • Let Routespring derive NEW_BOOKING, MODIFY, and CANCEL work from the difference.
  • Do not reuse a stale flight option ID or pagination cursor.
  • Do not assume an empty action list means processing is finished; check poll_recommended first.
  • Preserve manual_override on bookings that an operator intentionally changed during an exception.

5. Send evidence to operations and finance

Persist the schedule ID, action IDs, booking IDs, operational anchors, supplier confirmations, contracted rates, booked rates, and final states. The same records support day-of-ops visibility and the finance matching workflow.