Skip to main content

Leon API Integration Guide: Flight Data Synchronization

Version: 2026-03-23 API: Leon GraphQL API (Beta) Prepared by: Leon Software Support


What this guide does

This guide explains how to synchronize flight data from Leon into an external system — covering both the initial full data load and ongoing incremental updates using change-detection queries.


Before you start

Make sure you have:

  • Leon account with API access enabled — contact Leon Support if you don't have it yet
  • Application registered with Leon and client_id / client_secret received
  • Redirect URI confirmed and matching the one submitted at registration
  • GraphQL endpoint: https://{oprId}.leon.aero/api/graphql/
  • Confirmed which date range you want for the initial sync

⚠️ This guide uses OAuth 2.0 Code Grant — the mandatory authentication method for all 3rd party software vendors and integrators building for multiple Leon operators. If you are a single operator building an internal-only script, you may use the API Key method instead — contact Leon Support for guidance.


Synchronization strategy overview

The integration runs in two phases:

Phase 1 — Initial Sync
└── Split target window into 3-month chunks
└── Execute flightList for each chunk sequentially
└── Upsert results; record pre-sync UTC time as lastSyncTimestamp

Phase 2 — Incremental Sync (runs periodically, e.g. every 5–15 minutes)
├── flights { getModifiedFlightList(dateTime: $lastSyncTimestamp) }
│ └── returns: created[], changed[], deleted[], timestamp
├── (optional) flights { getFlightListOnWhichModifiedCrew(dateTime: ...) }
└── (optional) flights { getFlightListOnWhichModifiedPassengerList(dateTime: ...) }

The key value to persist between runs is the timestamp returned by getModifiedFlightList. Store it after every successful sync and pass it as dateTime in the next call.


All steps at a glance

  1. Authenticate → get access_token
  2. Initial sync: query flightList → get full flight dataset
  3. Store timestamp from step 2 response for incremental use
  4. Incremental sync: query flights { getModifiedFlightList } → process created, changed, deleted
  5. (Optional) Detect crew changes: flights { getFlightListOnWhichModifiedCrew }
  6. (Optional) Detect passenger changes: flights { getFlightListOnWhichModifiedPassengerList }

Step 1 — Authenticate via OAuth 2.0 Code Grant

OAuth authentication happens once per operator during the onboarding of that operator to your application. The resulting refresh_token is then stored and used to obtain short-lived access_tokens for all subsequent API calls.

Step 1a — Register your application (one-time, before development)

Submit the Leon application registration form: https://leonsoftware.atlassian.net/servicedesk/customer/portal/4/group/8/create/40

You will need to provide: application name, type, redirect URI, description, company logo, and contact details.

Leon will issue you a client_id and client_secret. Development and testing can be done on sandbox.leon.aero. Production access requires a demo meeting with Leon after development is complete.

Save this → client_id and client_secret — store securely, never expose in client-side code.


Step 1b — Request authorization from the operator (per-operator, one-time)

When a Leon operator wants to connect to your application, redirect their admin user to the following URL in their browser:

https://{oprId}.leon.aero/oauth2/code/authorize/?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE_LIST}&state={OPTIONAL_STATE}

Fill in:

PlaceholderWhere to get it
{oprId}The operator's Leon subdomain — ask any user: "What address do you use to sign in to Leon?"
{CLIENT_ID}Your client_id from Step 1a
{REDIRECT_URI}Your redirect URI — must exactly match what you submitted at registration
{SCOPE_LIST}Space-delimited list of required scopes (see Required scopes below)
{OPTIONAL_STATE}Optional CSRF token — recommended for security

Required scopes for flight synchronization:

ScopeRequired for
GRAPHQL_FLIGHTAll flight queries (flightList, getModifiedFlightList, etc.)
GRAPHQL_ACFTAircraft data (acft.registration, etc.)
GRAPHQL_CREW_MEMBERCrew assignments (crewMemberList)
GRAPHQL_PASSENGERPassenger list (passengerList)

After the operator grants consent in their browser, Leon redirects to your {REDIRECT_URI} with a code parameter in the query string.

Example redirect:

https://your-app.example.com/callback?code=AbCdEf123456&state=yourCsrfToken

Save this → code from the redirect URL — valid for 10 minutes, single-use.


Step 1c — Exchange the authorization code for tokens

curl --location --request POST 'https://{oprId}.leon.aero/oauth2/code/token/' \
--form 'grant_type="authorization_code"' \
--form 'client_id="{CLIENT_ID}"' \
--form 'client_secret="{CLIENT_SECRET}"' \
--form 'redirect_uri="{REDIRECT_URI}"' \
--form 'code="{AUTHORIZATION_CODE}"'

⚠️ Note the trailing slash in the URL — it is required.

Fill in:

PlaceholderWhere to get it
{oprId}The operator's Leon subdomain
{CLIENT_ID}Your client_id from Step 1a
{CLIENT_SECRET}Your client_secret from Step 1a
{REDIRECT_URI}Same URI as in Step 1b
{AUTHORIZATION_CODE}The code value from Step 1b redirect

Response:

{
"token_type": "Bearer",
"access_token": "eyJhbGc...",
"refresh_token": "dGhpcyBp..."
}

Save this →

  • access_token — use in all API requests (valid 30 minutes)
  • refresh_token — use to get a new access token when it expires (valid 30 days since last use); store securely per operator

Step 1d — Refresh the access token (when expired)

Run this every ~25 minutes (before the 30-minute access token expires) or whenever you receive a 401 Unauthorized response:

curl --location --request POST 'https://{oprId}.leon.aero/oauth2/code/token/' \
--form 'grant_type="refresh_token"' \
--form 'client_id="{CLIENT_ID}"' \
--form 'client_secret="{CLIENT_SECRET}"' \
--form 'refresh_token="{REFRESH_TOKEN}"'

Fill in:

PlaceholderWhere to get it
{oprId}The operator's Leon subdomain
{CLIENT_ID}Your client_id from Step 1a
{CLIENT_SECRET}Your client_secret from Step 1a
{REFRESH_TOKEN}The refresh_token saved in Step 1c

Response: same structure as Step 1c — a new access_token (and optionally a new refresh_token).

Save this → new access_token — replace the previous one.

⚠️ Token management rules:

  • Reuse the access token for its full 30-minute validity — do NOT create a new token per request
  • Limit: 500 active access tokens per refresh token — exceeding this returns HTTP 429 Too Many Requests
  • If you receive 429, check the Retry-After header before retrying
  • Store one refresh_token per connected operator — do not share tokens across operators

Step 2 — Initial sync: pull all flights in 3-month chunks

Use flightList to fetch all flights within a time window. This is a one-time bootstrap — after this, incremental sync handles updates.

Why chunking?

flightList returns all flights in a single response with no pagination. For operators with large schedules or long historical periods, requesting a wide time range in a single call risks timeouts and oversized payloads. Split the full target window into sequential 3-month chunks and execute one query per chunk.

Example: syncing 1 year of data (2025-01-01 → 2025-12-31)

Chunkstartend
12025-01-01T00:00:00Z2025-03-31T23:59:59Z
22025-04-01T00:00:00Z2025-06-30T23:59:59Z
32025-07-01T00:00:00Z2025-09-30T23:59:59Z
42025-10-01T00:00:00Z2025-12-31T23:59:59Z

Execute chunks sequentially (not in parallel) and upsert results into your database before moving to the next chunk. Record the wall-clock UTC time before starting chunk 1 — you will need it in Step 3.

⚠️ Do not overlap chunk boundaries. Use the end of one chunk as exactly one second before the start of the next (e.g. 03-31T23:59:59Z04-01T00:00:00Z) to avoid counting the same flight twice.


Request (repeat for each chunk)

POST https://{oprId}.leon.aero/api/graphql/
Content-Type: application/json
Authorization: Bearer <access_token from Step 1d>

Body:

query InitialFlightSync($filter: FlightFilter!) {
flightList(filter: $filter) {
flightNid
flightNo
status
flightType
isCnl
isFerry
startTimeUTC
endTimeUTC
flightLastModificationTime # store this — used to detect stale records
startAirport {
locationNid
name
code {
icao
iata
}
}
endAirport {
locationNid
name
code {
icao
iata
}
}
acft {
aircraftNid
registration
}
crewMemberList {
loginNid
isCaptain
isFirstOfficer
isFlightAttendant
position {
name
posType
}
contact {
contactNid
name
surname
}
}
passengerList {
count
passengerContactList {
contact {
contactNid
name
surname
}
}
}
trip {
tripNid
tripNumber
}
}
}

📖 Full type reference → Flight 📖 Full type reference → FlightFilter

Fill in (per chunk):

VariableTypeRequiredExample (chunk 1)Notes
$filter.timeInterval.startDateTime!Yes"2025-01-01T00:00:00Z"Start of this chunk, ISO 8601 UTC
$filter.timeInterval.endDateTime!Yes"2025-03-31T23:59:59Z"End of this chunk — 3 months later, last second of the day
$filter.flightStatus[FlightStatus]No["CONFIRMED", "OPTION"]Filter by status: CONFIRMED, OPTION, OPPORTUNITY
$filter.isCnlBooleanNofalsePass false to exclude cancelled flights
$filter.aircraftNidList[AircraftNid]No[12, 34]Limit to specific aircraft

Variables JSON — Chunk 1:

{
"filter": {
"timeInterval": {
"start": "2025-01-01T00:00:00Z",
"end": "2025-03-31T23:59:59Z"
},
"flightStatus": ["CONFIRMED", "OPTION"],
"isCnl": false
}
}

Variables JSON — Chunk 2:

{
"filter": {
"timeInterval": {
"start": "2025-04-01T00:00:00Z",
"end": "2025-06-30T23:59:59Z"
},
"flightStatus": ["CONFIRMED", "OPTION"],
"isCnl": false
}
}

(Continue the same pattern for each subsequent chunk until the full window is covered.)


Response (same structure for every chunk)

{
"data": {
"flightList": [
{
"flightNid": "abc123",
"flightNo": "LNS001",
"status": "CONFIRMED",
"flightType": "PAX",
"isCnl": false,
"startTimeUTC": "2025-02-15T08:00:00Z",
"endTimeUTC": "2025-02-15T10:30:00Z",
"flightLastModificationTime": "2025-02-10T14:22:00Z",
"startAirport": { "name": "Warsaw Chopin", "code": { "icao": "EPWA", "iata": "WAW" } },
"endAirport": { "name": "London Heathrow", "code": { "icao": "EGLL", "iata": "LHR" } },
"acft": { "aircraftNid": 42, "registration": "SP-LRA" }
}
]
}
}

After each chunk:

  • Upsert all returned flightNid records into your local database
  • An empty flightList is normal — it means no flights exist in that window
  • If a chunk times out or fails, do not advance to the next chunk — retry the failed chunk with a fresh access_token before continuing

Save this → After all chunks complete successfully, record the UTC timestamp noted before chunk 1 started — this becomes your lastSyncTimestamp for Step 3


Step 3 — Store the sync checkpoint

Before moving to incremental sync, record the timestamp of when the initial sync was performed.

Store in your system:

{
"lastSyncTimestamp": "2026-03-23T12:00:00Z"
}

Use the UTC time at which you ran Step 2 (not a field from the response). This value is passed to getModifiedFlightList in Step 4 to catch any changes made after the initial pull.

Save this → lastSyncTimestamp — you will update this after every successful incremental run.


Step 4 — Incremental sync: detect modified flights

flights { getModifiedFlightList } returns all flights created, changed, or deleted since a given point in time. Run this on a schedule (e.g. every 5–15 minutes).

Request:

POST https://{oprId}.leon.aero/api/graphql/
Content-Type: application/json
Authorization: Bearer <access_token from Step 1d>

Body:

query IncrementalFlightSync($dateTime: DateTime!) {
flights {
getModifiedFlightList(dateTime: $dateTime) {
timestamp # save this — use as dateTime in the NEXT incremental run
created {
flightNid
flightNo
status
flightType
isCnl
isFerry
startTimeUTC
endTimeUTC
flightLastModificationTime
startAirport {
locationNid
name
code { icao iata }
}
endAirport {
locationNid
name
code { icao iata }
}
acft {
aircraftNid
registration
}
crewMemberList {
loginNid
isCaptain
isFirstOfficer
position { name posType }
contact { contactNid name surname }
}
passengerList {
count
passengerContactList {
contact { contactNid name surname }
}
}
trip {
tripNid
tripNumber
}
}
changed {
# same fields as created — full updated record is returned
flightNid
flightNo
status
flightType
isCnl
isFerry
startTimeUTC
endTimeUTC
flightLastModificationTime
startAirport {
locationNid
name
code { icao iata }
}
endAirport {
locationNid
name
code { icao iata }
}
acft {
aircraftNid
registration
}
crewMemberList {
loginNid
isCaptain
isFirstOfficer
position { name posType }
contact { contactNid name surname }
}
passengerList {
count
passengerContactList {
contact { contactNid name surname }
}
}
trip {
tripNid
tripNumber
}
}
deleted # list of FlightNid scalars — remove these from your system
}
}
}

📖 Full type reference → FlightsChanges 📖 Full type reference → FlightsQuery

Fill in:

VariableTypeRequiredExampleNotes
$dateTimeDateTime!Yes"2026-03-23T12:00:00Z"The lastSyncTimestamp saved in Step 3 (or updated after the previous incremental run)

Variables JSON:

{
"dateTime": "2026-03-23T12:00:00Z"
}

Response structure:

{
"data": {
"flights": {
"getModifiedFlightList": {
"timestamp": 1742730000,
"created": [ { "flightNid": "xyz789", ... } ],
"changed": [ { "flightNid": "abc123", ... } ],
"deleted": [ "def456" ]
}
}
}
}

Processing logic:

FieldAction
createdInsert new flight records into your database
changedUpdate existing records matched by flightNid
deletedRemove or mark as deleted records matched by flightNid
timestampStore this as the new lastSyncTimestamp for the next run

⚠️ Important: Always update lastSyncTimestamp to the timestamp value returned in the response (it is a Unix timestamp integer). Do not use the current wall clock time — use the value Leon returns to avoid missing changes that occurred during query execution.

Save this → timestamp from the response → store as your new lastSyncTimestamp.


Step 5 — (Optional) Detect crew assignment changes

If your external system tracks crew separately, use getFlightListOnWhichModifiedCrew to get the list of flights where crew was modified since a given time.

This query returns only changed flights (no created/deleted) — it signals which flights had crew roster updates, so you can re-pull those flights or update crew data specifically.

Body:

query CrewChanges($dateTime: DateTime!) {
flights {
getFlightListOnWhichModifiedCrew(dateTime: $dateTime) {
timestamp
changed {
flightNid
flightNo
startTimeUTC
endTimeUTC
crewMemberList {
loginNid
isCaptain
isFirstOfficer
isFlightAttendant
position { name posType }
contact { contactNid name surname }
}
}
}
}
}

📖 Full type reference → FlightsDataChanges

Variables JSON:

{
"dateTime": "2026-03-23T12:00:00Z"
}

Save this → timestamp from the response (use the same lastSyncTimestamp management as Step 4, or maintain a separate crew-sync checkpoint if needed).


Step 6 — (Optional) Detect passenger list changes

Use getFlightListOnWhichModifiedPassengerList to get flights where the passenger list changed since a given time.

Body:

query PassengerChanges($dateTime: DateTime!) {
flights {
getFlightListOnWhichModifiedPassengerList(dateTime: $dateTime) {
timestamp
changed {
flightNid
flightNo
startTimeUTC
endTimeUTC
passengerListCount
passengerList {
count
passengerContactList {
contact {
contactNid
name
surname
}
}
}
}
}
}
}

Variables JSON:

{
"dateTime": "2026-03-23T12:00:00Z"
}

Sync typeRecommended intervalNotes
Initial syncOnce at integration setupRe-run if data appears inconsistent
getModifiedFlightListEvery 5–15 minutesCore incremental sync
getFlightListOnWhichModifiedCrewEvery 5–15 minutesOnly if tracking crew separately
getFlightListOnWhichModifiedPassengerListEvery 5–15 minutesOnly if tracking pax separately
Token refreshEvery 25 minutesAccess token valid for 30 min

Key field reference

FieldTypeDescription
flightNidFlightNid (scalar)Unique identifier for a flight leg — use as your primary key
startTimeUTC / endTimeUTCDateTimeScheduled departure / arrival in UTC
flightLastModificationTimeDateTimeLast time this flight record was modified in Leon
statusFlightStatus enumCONFIRMED, OPTION, or OPPORTUNITY
flightTypeFlightType enumE.g. PAX, CARGO, OWNER, TRAINING, etc.
isCnlBooleantrue if the flight has been cancelled
isFerryBooleantrue if this is a ferry (positioning) leg
acft.aircraftNidAircraftNidUnique aircraft identifier
trip.tripNidIntGroups multiple legs into one trip
FlightsChanges.timestampInt (Unix)Use as dateTime input for the next incremental call

If something goes wrong

SymptomLikely causeFix
401 UnauthorizedToken expired or missingRe-run Step 1d to refresh the access_token
429 Too Many RequestsTokens created too frequentlyReuse the same token for 30 min; check Retry-After header
400 Bad RequestWrong variable type or formatCheck that dateTime is ISO 8601 UTC, e.g. "2026-03-23T12:00:00Z"
Empty created / changed / deletedNo changes since dateTimeNormal — no action needed; save the new timestamp anyway
changed contains flights outside your time windowExpected — Leon returns all modified flights regardless of dateFilter client-side if needed using startTimeUTC
Missing flights after initial syncTime window too narrow or token expired mid-runWiden timeInterval or re-run with a fresh token

❌ What this integration cannot do (yet)

You asked forStatusNotes
Push notifications / webhooks for flight changesNot covered in this guideLeon has a webhook module — contact Leon Support for configuration
Filter getModifiedFlightList by aircraft or date range❌ Not supportedThe query accepts only dateTime (and optional operatorBaseNameList); filter client-side after fetching
Write flight data back to Leon via this flow❌ Not in scopeCreating or updating flights requires separate mutation workflows