Subscriptions and Streaming

On rare occations, some API operations can take up to 30 seconds to complete. Instead of showing a blank loading screen, the API streams results as they become available. This lets you build better user experiences with progressive updates and real-time feedback.

Why Streaming Matters

Train operators sometimes suffer from long loading times. When you request pricing or create a booking, the API needs to:

  • Contact multiple train operators
  • Check real-time availability
  • Calculate prices
  • Pre-book tickets

This can take up to 30 seconds, too long for users to stare at a loading spinner. Streaming solves this by sending data as soon as it's available.

How Streaming Works

The API supports streaming in two ways:

  1. WebSocket connections: Keep a connection open and receive response without worrying about conneciton timeout
  2. GraphQL subscriptions: Subscribe to specific queries and receive incremental updates

When to Use Streaming

Use WebSockets or subscriptions for operations that can be slow:

  • Finding journeys: Itineraries are streamed as the routing engine finds the optimal route
  • Getting journey offers: Prices stream in as each segment is priced
  • Creating bookings, payments, or orders: These operations do not stream updates, but doing them over WebSockets is a good way to avoid the risk of connection timeout

For fast operations (like fetching passes), regular HTTP requests are fine.

Using Subscriptions

Subscriptions are GraphQL queries that return multiple results over time. The most common use case is getting journey offers, where prices arrive incrementally.

When you subscribe to getJourneyOffer, you receive updates as:

  1. Journey segments are found
  2. Prices are calculated for each segment
  3. The complete offer is assembled

Each update includes a status field showing what's complete and what's still loading.

Example: Subscribe to journey offers
js
import { createClient } from 'graphql-ws'

const client = createClient({
  url: 'wss://api-gateway.allaboard.eu',
  connectionParams: {
    'api-key': 'AGENT_API_KEY'
  }
})

const query = `
  subscription GetJourneyOffer(
    $journey: ID!
    $passengers: [PassengerPlaceholderInput!]!
  ) {
    getJourneyOffer(journey: $journey, passengers: $passengers) {
      status
      itinerary {
        __typename
        ... on SegmentCollection {
          status
          segments {
            departureAt
            origin {
              name
              countryCode
            }
          }
          offers {
            price {
              amount
              currency
            }
            parts {
              __typename
              ... on AdmissionPart {
                flexibility
                serviceClass
                comfortClass
              }
              ... on ReservationPart {
                flexibility
                comfortClass
                accommodation {
                  type
                }
              }
            }
          }
        }
        ... on Stopover {
          location {
            name
            countryCode
          }
        }
      }
    }
  }
`

const variables = {
  journey: 'e18aec5a-7986-4d89-b381-f0a7f283d41e',
  passengers: [{ type: 'ADULT' }]
}

const subscription = client.iterate({ query, variables })

for await (const data of subscription) {
  console.log(data)
  // Update your UI with the latest data
}
import { createClient } from 'graphql-ws'

const client = createClient({
  url: 'wss://api-gateway.allaboard.eu',
  connectionParams: {
    'api-key': 'AGENT_API_KEY'
  }
})

const query = `
  subscription GetJourneyOffer(
    $journey: ID!
    $passengers: [PassengerPlaceholderInput!]!
  ) {
    getJourneyOffer(journey: $journey, passengers: $passengers) {
      status
      itinerary {
        __typename
        ... on SegmentCollection {
          status
          segments {
            departureAt
            origin {
              name
              countryCode
            }
          }
          offers {
            price {
              amount
              currency
            }
            parts {
              __typename
              ... on AdmissionPart {
                flexibility
                serviceClass
                comfortClass
              }
              ... on ReservationPart {
                flexibility
                comfortClass
                accommodation {
                  type
                }
              }
            }
          }
        }
        ... on Stopover {
          location {
            name
            countryCode
          }
        }
      }
    }
  }
`

const variables = {
  journey: 'ba47500e-2e11-4487-8bae-cfc1d98714bd',
  passengers: [{ type: 'ADULT' }]
}

const subscription = client.iterate({ query, variables })

for await (const data of subscription) {
  console.log(data)
  // Update your UI with the latest data
}

Understanding Status Updates

As data streams in, you'll see status updates that tell you what's happening:

First message - Journey structure is known, but prices aren't ready yet:

json
{
  "status": "LOADING",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "LOADING",
      "segments": [
        {
          "departureAt": "2023-08-01T09:42:00",
          "origin": { "name": "Amsterdam", "countryCode": "NL" }
        }
      ]
    }
  ]
}
{
  "status": "LOADING",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "LOADING",
      "segments": [
        {
          "departureAt": "2023-08-01T09:42:00",
          "origin": { "name": "Amsterdam", "countryCode": "NL" }
        }
      ]
    }
  ]
}

Second message - First segment is priced, others still loading:

json
{
  "status": "LOADING",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [
        {
          "price": { "amount": 13400, "currency": "EUR" },
          "parts": [...]
        }
      ]
    },
    {
      "__typename": "SegmentCollection",
      "status": "LOADING",
      "segments": [...]
    }
  ]
}
{
  "status": "LOADING",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [
        {
          "price": { "amount": 13400, "currency": "EUR" },
          "parts": [...]
        }
      ]
    },
    {
      "__typename": "SegmentCollection",
      "status": "LOADING",
      "segments": [...]
    }
  ]
}

Final message - All segments priced, complete offer ready:

json
{
  "status": "SUCCESS",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [...]
    },
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [...]
    }
  ]
}
{
  "status": "SUCCESS",
  "itinerary": [
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [...]
    },
    {
      "__typename": "SegmentCollection",
      "status": "SUCCESS",
      "segments": [...],
      "offers": [...]
    }
  ]
}

Using WebSockets for Mutations

Even if you're not using subscriptions, use WebSocket connections for slow mutations to avoid timeouts:

Depending on your server set-up, regular HTTP requests may timeout on these operations. WebSocket connections handle long-running requests better.

The All Aboard API does not impose any connection time limits. As long as the client remains connected, any regular HTTP POST or GET request will continue processing until a result is returned.

Best Practices

  1. Show progress: Use status updates to show what's happening ("Checking prices...", "Booking tickets...")

  2. Display partial results: Show offers as they arrive instead of waiting for everything

  3. Handle errors gracefully: If a segment fails, show which parts succeeded and which didn't

  4. Use WebSockets for slow operations: Even without subscriptions, WebSockets prevent timeouts

  5. Provide feedback: Let users know the operation is in progress, especially for long-running operations

Next Steps