ACARS V2 API — Device Code Authentication
The V2 ACARS API introduces a device code authentication flow, replacing the traditional username/password login. This allows ACARS desktop clients to authenticate securely without handling user credentials.
Authentication Flow
sequenceDiagram
participant Client as ACARS Client
participant API as Airspace API
participant Pilot as Pilot (Browser)
Client->>API: POST /api/v2/acars/auth/request
API-->>Client: { user_code, authorization_token, expires_in, poll_interval }
Client->>Pilot: Display "Enter code at /acars/authorize"
loop Every poll_interval seconds
Client->>API: POST /api/v2/acars/auth/token
API-->>Client: 202 Pending
end
Pilot->>API: Enters code on /acars/authorize
Note over API: Code approved
Client->>API: POST /api/v2/acars/auth/token
API-->>Client: 200 { access_token, token_type }
Client->>API: GET /api/v2/acars/va (Bearer token)
API-->>Client: VA data
Step 1: Request a Device Code
POST /api/v2/acars/auth/request
No authentication required. No request body.
Response (201 Created):
{
"user_code": "482910",
"authorization_token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"expires_in": 300,
"poll_interval": 5
}
| Field | Type | Description |
|---|---|---|
user_code | string | 6-digit code to display to the pilot |
authorization_token | string | 64-character hex token (keep secret, used for polling) |
expires_in | integer | Seconds until the code expires (300 = 5 minutes) |
poll_interval | integer | Minimum seconds between poll requests |
Step 2: User Approves the Code
The pilot navigates to /acars/authorize in their browser (must be logged in to the VA), enters the 6-digit code, and clicks "Authorize Device".
The code can also be pre-filled via query parameter: /acars/authorize?code=482910.
Step 3: Poll for Access Token
POST /api/v2/acars/auth/token
Content-Type: application/json
{
"authorization_token": "a1b2c3d4e5f6..."
}
| Parameter | Type | Required | Description |
|---|---|---|---|
authorization_token | string | Yes | The 64-character token from step 1 |
Response Codes:
200 OK — Approved
{
"access_token": "1|abc123def456...",
"token_type": "Bearer"
}
The device has been approved. Use the access_token as a Bearer token for all authenticated endpoints.
202 Accepted — Pending
{
"status": "pending"
}
The user hasn't approved the code yet. Continue polling every poll_interval seconds.
410 Gone — Expired
{
"status": "expired"
}
The code has expired or doesn't exist. Start a new device code flow from step 1.
429 Too Many Requests — Slow Down
{
"status": "slow_down"
}
You're polling too frequently. Wait at least poll_interval seconds between requests.
Authenticated Endpoints
Once you have an access_token, include it in the Authorization header:
Authorization: Bearer 1|abc123def456...
All V2 endpoints below require this header unless marked as public.
Pilot Profile & History
GET /api/v2/acars/pilot
Returns the authenticated pilot's profile information and 6-month flight history in a single call. This is the recommended endpoint for building pilot dashboards.
Response (200):
{
"profile": {
"id": 42,
"name": "John Doe",
"email": "[email protected]",
"profile_picture_url": "https://...",
"member_since": "2025-01-15T00:00:00+00:00",
"rank": { "name": "Captain", "level": 5, "color": "#FFD700", "icon": "captain.svg" },
"current_airport": { "icao": "KJFK", "iata": "JFK", "name": "John F. Kennedy Intl", "city": "New York" },
"base_airport": { "icao": "KLAX", "iata": "LAX", "name": "Los Angeles Intl", "city": "Los Angeles" },
"vatsim_id": "1234567",
"ivao_id": null,
"totals": {
"flights": 150,
"flight_hours": 320.5,
"points": 4500,
"avg_landing_rate": -185.3,
"last_flight_at": "2026-03-01T14:30:00+00:00"
}
},
"history": {
"months": ["2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"],
"flight_hours": [12.5, 8.3, 15.0, 6.2, 10.1, 3.5],
"flights": [5, 3, 7, 2, 4, 1],
"points": [500, 300, 700, 200, 400, 100],
"avg_landing_rate": [-180, -200, -150, -220, -170, -190]
}
}
| Field | Type | Description |
|---|---|---|
profile.rank | object | null | Pilot's current rank (null if unranked) |
profile.current_airport | object | null | Airport where the pilot is currently located |
profile.base_airport | object | null | Pilot's assigned home base |
profile.totals | object | Lifetime statistics |
history | object | 6-month monthly breakdown (same format as /history) |
Also available at the V1 path: GET /api/acars/pilot
VA Information
GET /api/v2/acars/va
Returns the virtual airline's name, logos, primary color, and domains.
Response (200):
{
"name": "Example Airways",
"logo_url": "https://...",
"banner_url": "https://...",
"primary_color": "#1a73e8",
"domains": ["example.airspace.app"]
}
Active Booking
GET /api/v2/acars/booking
Returns the authenticated pilot's active booking with airline, aircraft, and airport details.
Response (200):
{
"id": 42,
"flight_number": "EX101",
"callsign": "EXA101",
"is_charter": false,
"is_alternating": false,
"expires_at": "2026-02-24T15:30:00+00:00",
"airline": { "name": "Example Airways", "icao": "EXA", "iata": "EX" },
"aircraft": { "registration": "N12345", "name": "Spirit of Adventure", "type": "B738", "subfleet": "738", "fleet": "Boeing 737" },
"departure_airport": { "icao": "KJFK", "iata": "JFK", "name": "John F. Kennedy Intl", "city": "New York", "latitude": 40.6398, "longitude": -73.7789, "elevation": 13 },
"arrival_airport": { "icao": "KLAX", "iata": "LAX", "name": "Los Angeles Intl", "city": "Los Angeles", "latitude": 33.9425, "longitude": -118.4081, "elevation": 128 },
"alternate_airport": null
}
Last Position
GET /api/v2/acars/position/last
Returns the latest position report for the active booking.
Response (200):
{
"sent_at": "2026-02-24T14:30:00.000+00:00",
"latitude": 40.6398,
"longitude": -73.7789,
"altitude": 35000,
"altitude_agl": 34500,
"heading": 270,
"ground_speed": 450,
"indicated_airspeed": 280,
"true_airspeed": 460,
"vertical_speed": 0,
"phase": "cruise",
"on_ground": false
}
Start Flight
POST /api/v2/acars/start
Marks the active booking as started and clears prior position reports.
Response (200):
{
"message": "Flight started",
"trackingID": 42
}
Submit Position Report
POST /api/v2/acars/position
Content-Type: application/json
Accepts a single position object or an array of position objects. Returns 202 immediately; processing is asynchronous.
Single position:
{
"timestamp": "2026-03-08T12:00:00.123Z",
"position": { "latitude": { "value": 51.4775 }, "longitude": { "value": -0.4614 } },
"..."
}
Batch (array of positions):
[
{ "timestamp": "2026-03-08T12:00:00.000Z", "position": { "latitude": { "value": 51.0 }, "longitude": { "value": -0.5 } }, "..." },
{ "timestamp": "2026-03-08T12:00:00.250Z", "position": { "latitude": { "value": 51.5 }, "longitude": { "value": -0.6 } }, "..." }
]
timestamp is stored with millisecond precision (timestamp(3) in MySQL/Postgres). All of the following formats are accepted, so existing clients that report at whole-second resolution continue to work unchanged:
- ISO-8601 string with fractional seconds —
"2026-03-08T12:00:00.123Z" - ISO-8601 string with whole seconds —
"2026-03-08T12:00:00Z" - Unix epoch seconds (integer or numeric string) —
1741435200 - Unix epoch milliseconds (13-digit integer or numeric string) —
1741435200123
The server parses the format automatically: any numeric value ≥ 1e12 is treated as milliseconds since epoch, anything below that as seconds. Response timestamps are always returned in ISO-8601 with the .vvv fractional component.
Key fields (per position):
| Field | Type | Required | Description |
|---|---|---|---|
latitude | float | Yes | Current latitude |
longitude | float | Yes | Current longitude |
altitude | int | No | Altitude MSL (feet) |
altitudeAgl | int | No | Altitude AGL (feet) |
heading | int | No | True heading (degrees) |
groundSpeed | int | No | Ground speed (knots) |
indicatedAirspeed | int | No | IAS (knots) |
trueAirspeed | int | No | TAS (knots) |
verticalSpeed | int | No | VS (fpm) |
flightPhase | string | No | Current phase |
onGround | bool | No | On ground flag |
landingRate | float | No | Landing VS (fpm) |
aircraftType | string | No | Aircraft ICAO type code (e.g. B738) |
Engine fields:
Each engine (1–4) supports the following nested fields:
| Field | Type | Description |
|---|---|---|
engines[].n1 | float | N1 percentage |
engines[].n2 | float | N2 percentage |
engines[].throttle | float | Throttle position |
engines[].mixture | float | Mixture lever position |
engines[].propeller | float | Propeller lever position |
engines[].firing | bool | Engine running |
engines[].exists | bool | Engine physically present on aircraft |
Autopilot fields:
| Field | Type | Description |
|---|---|---|
autopilot.master | bool | Autopilot master switch |
autopilot.heading | float | Selected heading |
autopilot.altitude | float | Selected altitude |
autopilot.vs | float | Selected vertical speed |
autopilot.speed | float | Selected speed |
autopilot.approach | bool | Approach mode active |
autopilot.nav | bool | NAV mode active |
Radio & navigation fields:
| Field | Type | Description |
|---|---|---|
radios.com1 | float | COM1 frequency |
radios.com2 | float | COM2 frequency |
radios.nav1 | float | NAV1 frequency |
radios.nav2 | float | NAV2 frequency |
radios.nav1Obs | float | NAV1 OBS setting |
radios.nav2Obs | float | NAV2 OBS setting |
radios.transponder | int | Transponder code |
radios.transponderState | int | Transponder state (0=off, 1=standby, 2=on, 3=test) |
Lights fields:
| Field | Type | Description |
|---|---|---|
lights.beacon | bool | Beacon light |
lights.strobe | bool | Strobe lights |
lights.landing | bool | Landing lights |
Controls fields:
| Field | Type | Description |
|---|---|---|
controls.elevator | float | Elevator deflection |
controls.aileron | float | Aileron deflection |
controls.rudder | float | Rudder deflection |
controls.spoilers | float | Spoilers position |
APU fields:
| Field | Type | Description |
|---|---|---|
apu.switchOn | bool | APU master switch |
apu.rpm | float | APU RPM percentage |
apu.genSwitch | bool | APU generator switch |
apu.genActive | bool | APU generator active |
Doors fields:
| Field | Type | Description |
|---|---|---|
doors[].open | float | Door open position (0.0 = closed, 1.0 = fully open) |
Up to 5 doors are supported (doors[0] through doors[4]).
Environment & state fields:
| Field | Type | Description |
|---|---|---|
wind.direction | float | Wind direction (degrees true) |
wind.speed | float | Wind speed (knots) |
sensors.pressureQNH | float | QNH pressure setting |
sensors.gForce | float | Current G-force |
sensors.stallWarning | bool | Stall warning active |
sensors.overspeedWarning | bool | Overspeed warning active |
sensors.paused | bool | Simulation paused |
sensors.slew | bool | Slew mode active |
sensors.crashed | bool | Aircraft crashed |
simulationRate | float | Simulation rate multiplier |
totalWeight | float | Aircraft total weight (lbs) |
fuelWeight | float | Fuel weight (lbs) |
See the Scramble API docs at https://<your-domain>/docs/acars for the complete field schema and validation rules.
Response (202):
{
"message": "Position received"
}
or for batch:
{
"message": "3 positions received"
}
Cancel Flight
POST /api/v2/acars/stop
Stops the active flight and clears all position reports.
Response (200):
{
"message": "Flight canceled"
}
Finish Flight
POST /api/v2/acars/finish
Completes the flight, creates a PIREP, and deletes the booking.
Response (200):
{
"message": "Flight finished",
"pirepID": 123
}
Speech Instructions
GET /api/v2/acars/sound
Returns pending TTS speech instructions for the active booking.
Response (200):
{
"instructions": [
{ "type": "play", "url": "/api/v2/acars/sound/fragment/42" },
{ "type": "pause", "duration_ms": 500 },
{ "type": "play", "url": "/api/v2/acars/sound/combined/abc123.mp3" }
]
}
Audio Streaming (Public)
These endpoints do not require authentication:
GET /api/v2/acars/sound/fragment/{id}
GET /api/v2/acars/sound/combined/{filename}
GET /api/v2/acars/sound/upload/{filename}
Returns audio/mpeg streams.
Send Message
POST /api/v2/acars/message
Content-Type: application/json
{
"message": "Requesting pushback clearance."
}
Response (201):
{
"id": 1,
"user_id": 5,
"type": "chat",
"sender_id": 5,
"sender_name": "John Doe",
"sender_role": "user",
"message": "Requesting pushback clearance.",
"sent_at": "2026-02-24T14:30:00.000000Z",
"received_at": "2026-02-24T14:30:00.000000Z",
"read_at": null,
"created_at": "2026-02-24T14:30:00.000000Z",
"updated_at": "2026-02-24T14:30:00.000000Z"
}
Confirm Message
PUT /api/v2/acars/message/confirm
Content-Type: application/json
{
"message_id": 1
}
Marks a message as read. Returns the updated message object.
List Messages
GET /api/v2/acars/messages?page=1
Returns paginated messages (50 per page), newest first.
Interactive API Documentation
Full interactive API documentation with request/response schemas is available at:
https://<your-domain>/docs/acars
This is auto-generated by Scramble and includes both V1 and V2 endpoints.
Pilot History (Legacy)
Returns 6 months of the authenticated pilot's flight statistics — hours, flights, points, and average landing rate. Prefer /pilot which includes profile data alongside history.
GET /api/v2/acars/history
Authorization: Bearer <token>
Response:
{
"months": ["2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"],
"flight_hours": [12.5, 8.3, 15.0, 6.2, 10.1, 3.5],
"flights": [5, 3, 7, 2, 4, 1],
"points": [500, 300, 700, 200, 400, 100],
"avg_landing_rate": [-180, -200, -150, -220, -170, -190]
}
| Field | Type | Description |
|---|---|---|
months | string[] | 6 month labels in YYYY-MM format (oldest first) |
flight_hours | float[] | Total accepted flight hours per month |
flights | int[] | Total accepted flight count per month |
points | int[] | Total points earned per month |
avg_landing_rate | float[] | Average landing rate (fpm) per month |
Also available at the V1 path: GET /api/acars/history
VA Information — favicon_url
The VA information endpoint now includes a favicon_url field:
GET /api/v2/acars/va
The favicon_url field returns the tenant's configured favicon as a fully resolved URL, or null if not set.
Error Handling
All endpoints return JSON error responses:
{
"message": "No active booking"
}
Common error codes:
| Code | Meaning |
|---|---|
| 401 | Missing or invalid Bearer token |
| 404 | Resource not found (no active booking, etc.) |
| 410 | Device code expired |
| 422 | Validation error |
| 429 | Rate limited (polling too fast) |