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_secretreceived - 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
- Authenticate → get
access_token - Initial sync: query
flightList→ get full flight dataset - Store
timestampfrom step 2 response for incremental use - Incremental sync: query
flights { getModifiedFlightList }→ processcreated,changed,deleted - (Optional) Detect crew changes:
flights { getFlightListOnWhichModifiedCrew } - (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:
| Placeholder | Where 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:
| Scope | Required for |
|---|---|
GRAPHQL_FLIGHT | All flight queries (flightList, getModifiedFlightList, etc.) |
GRAPHQL_ACFT | Aircraft data (acft.registration, etc.) |
GRAPHQL_CREW_MEMBER | Crew assignments (crewMemberList) |
GRAPHQL_PASSENGER | Passenger 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:
| Placeholder | Where 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:
| Placeholder | Where 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 theRetry-Afterheader before retrying- Store one
refresh_tokenper connected operator — do not share tokens across operators
Step 2 — Initial sync: pull all flights in 3-month chunks
Use
flightListto 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)
| Chunk | start | end |
|---|---|---|
| 1 | 2025-01-01T00:00:00Z | 2025-03-31T23:59:59Z |
| 2 | 2025-04-01T00:00:00Z | 2025-06-30T23:59:59Z |
| 3 | 2025-07-01T00:00:00Z | 2025-09-30T23:59:59Z |
| 4 | 2025-10-01T00:00:00Z | 2025-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:59Z→04-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):
| Variable | Type | Required | Example (chunk 1) | Notes |
|---|---|---|---|---|
$filter.timeInterval.start | DateTime! | Yes | "2025-01-01T00:00:00Z" | Start of this chunk, ISO 8601 UTC |
$filter.timeInterval.end | DateTime! | 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.isCnl | Boolean | No | false | Pass 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
flightNidrecords into your local database - An empty
flightListis 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_tokenbefore 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:
| Variable | Type | Required | Example | Notes |
|---|---|---|---|---|
$dateTime | DateTime! | 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:
| Field | Action |
|---|---|
created | Insert new flight records into your database |
changed | Update existing records matched by flightNid |
deleted | Remove or mark as deleted records matched by flightNid |
timestamp | Store this as the new lastSyncTimestamp for the next run |
⚠️ Important: Always update
lastSyncTimestampto thetimestampvalue 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
getFlightListOnWhichModifiedCrewto 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
getFlightListOnWhichModifiedPassengerListto 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"
}
Recommended polling schedule
| Sync type | Recommended interval | Notes |
|---|---|---|
| Initial sync | Once at integration setup | Re-run if data appears inconsistent |
getModifiedFlightList | Every 5–15 minutes | Core incremental sync |
getFlightListOnWhichModifiedCrew | Every 5–15 minutes | Only if tracking crew separately |
getFlightListOnWhichModifiedPassengerList | Every 5–15 minutes | Only if tracking pax separately |
| Token refresh | Every 25 minutes | Access token valid for 30 min |
Key field reference
| Field | Type | Description |
|---|---|---|
flightNid | FlightNid (scalar) | Unique identifier for a flight leg — use as your primary key |
startTimeUTC / endTimeUTC | DateTime | Scheduled departure / arrival in UTC |
flightLastModificationTime | DateTime | Last time this flight record was modified in Leon |
status | FlightStatus enum | CONFIRMED, OPTION, or OPPORTUNITY |
flightType | FlightType enum | E.g. PAX, CARGO, OWNER, TRAINING, etc. |
isCnl | Boolean | true if the flight has been cancelled |
isFerry | Boolean | true if this is a ferry (positioning) leg |
acft.aircraftNid | AircraftNid | Unique aircraft identifier |
trip.tripNid | Int | Groups multiple legs into one trip |
FlightsChanges.timestamp | Int (Unix) | Use as dateTime input for the next incremental call |
If something goes wrong
| Symptom | Likely cause | Fix |
|---|---|---|
401 Unauthorized | Token expired or missing | Re-run Step 1d to refresh the access_token |
429 Too Many Requests | Tokens created too frequently | Reuse the same token for 30 min; check Retry-After header |
400 Bad Request | Wrong variable type or format | Check that dateTime is ISO 8601 UTC, e.g. "2026-03-23T12:00:00Z" |
Empty created / changed / deleted | No changes since dateTime | Normal — no action needed; save the new timestamp anyway |
changed contains flights outside your time window | Expected — Leon returns all modified flights regardless of date | Filter client-side if needed using startTimeUTC |
| Missing flights after initial sync | Time window too narrow or token expired mid-run | Widen timeInterval or re-run with a fresh token |
❌ What this integration cannot do (yet)
| You asked for | Status | Notes |
|---|---|---|
| Push notifications / webhooks for flight changes | Not covered in this guide | Leon has a webhook module — contact Leon Support for configuration |
Filter getModifiedFlightList by aircraft or date range | ❌ Not supported | The query accepts only dateTime (and optional operatorBaseNameList); filter client-side after fetching |
| Write flight data back to Leon via this flow | ❌ Not in scope | Creating or updating flights requires separate mutation workflows |