Copilot EFB · public API

The Copilot EFB API

Read your own flight bag from a service of your own: aircraft, aerodrome cards, weather, notes, checklists, flight plans, the logbook and the pilot profile. And the one place where the RogerATC AI plugin keeps its session statistics.

Base URL
https://api.rogeratc.app
Authentication
Personal API key
Format
JSON over HTTPS
Version
1.11.0
01

What the API is

The Copilot EFB public API gives read access to one pilot's own data — aircraft, aerodrome cards with their frequencies, runways and taxiways, weather at their aerodromes, notes, checklists, routes planned on the map, flight plans, the logbook and the pilot profile — so a service of your own can use what you already keep in the flight bag.

It reads everything and writes two things, both from the RogerATC AI plugin for X-Plane: session summaries at PUT /v1/plugin/sessions/{id} and the live state at PUT /v1/plugin/live. Nothing a pilot wrote in the EFB can be changed or deleted through it, and any other method answers 405.

The key reads everything the account holds, including the pilot profile — licence numbers, the medical certificate, a telephone. Keep it on a server. The API sends no CORS headers, on purpose, so a web page cannot call it: never put the key in a web page or in a mobile app.

The live reference, generated from the API's own code, is at api.rogeratc.app/docs, and the machine-readable OpenAPI 3.1 description at api.rogeratc.app/openapi.json. This page is checked against a copy of that description every time the site is built.

02

Quick start

Copy the key from Copilot EFB → Settings → API access and try it:

export ROGER_API_KEY="rat_live_…"

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/me
curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/aircraft
curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/weather

GET /v1/me is the cheapest way to check that a key works: it says which account the key belongs to.

03

Your API key

Every Copilot EFB account has one personal key, from the moment the account is confirmed — there is nothing to apply for. It lives in Settings → API access, together with its usage.

MomentWhat happens
Account confirmedThe key is created. An account that predates keys gets one at its next sign-in.
Settings → API accessThe key is shown, with its first characters (prefix) to tell keys apart without revealing them.
RotateA new key replaces the old one, which stops working the moment the rotation returns. Every integration that used it needs the new one.
Account pausedThe key is suspended with the account and answers 401. Resuming the account restores it.
Account deletedThe key is deleted with everything else.

Format: rat_live_ followed by 43 base64url characters (32 random bytes). The API never returns the key itself: GET /v1/me shows only its prefix and its dates.

If somebody may have seen the key — a screenshot, a shared document, a commit — rotate it. Rotation is immediate and costs nothing but updating your integrations.

Usage statistics

Settings → API access also shows how the key was used: requests per day and per endpoint over up to 90 days, how many were errors, and when the key was last used. Counters are per UTC day and per route template, such as GET /v1/aircraft/{id}.

Every request to an existing route made with a valid key is counted, whatever its status — a 404 for a record that is not there, a 400, a 429 — because that is exactly what the statistics are for. A path that is not a route, and any request without a valid key, cannot be attributed to an account and is not counted.

04

Authentication

Send the key on every request, in either header:

Authorization: Bearer rat_live_x3V…
X-API-Key: rat_live_x3V…

Only GET /v1/health and the reference itself — /docs and /openapi.json — answer without a key.

A missing, malformed, unknown or rotated key, or the key of a paused or deleted account, answers 401 unauthorized with WWW-Authenticate: Bearer. The API does not say which of those it was.

05

Requests and responses

  • Methods: GET, and PUT on /v1/plugin/sessions/{id} and /v1/plugin/live only. HTTPS only (TLS 1.2 or later), JSON only.
  • Times are UTC, ISO 8601. Logbook durations are whole minutes.
  • A value the app does not know is null or absent — never invented.
  • Responses with data carry Cache-Control: no-store, private.
  • Every response carries X-Request-Id, the same value as meta.requestId. Quote it when something went wrong.

A single resource:

{
  "data": {
    "id": "ac-1",
    "registration": "LV-XXX",
    "icaoType": "C172",
    "updatedAt": "2026-09-17T11:16:29.900Z",
    "…": "…"
  },
  "meta": { "requestId": "Q2a8vg…", "generatedAt": "2026-09-17T15:02:11.410Z" }
}

A collection:

{
  "data": [ { "id": "…", "…": "…" } ],
  "page": { "limit": 50, "total": 2, "nextCursor": null },
  "meta": { "requestId": "…", "generatedAt": "…" }
}

What a record is

A record is returned whole: every field the app stores, exactly as the EFB synced it, plus id and updatedAt, which are always present. Deleted records are never returned.

A field the app adds is in the API the day it syncs, with no new version. Ignore fields you do not recognise. The examples on this page are trimmed; a real record carries more.

06

Errors

{
  "error": { "code": "not_found", "message": "No aircraft with id \"x\" in this account." },
  "meta": { "requestId": "…", "generatedAt": "…" }
}
StatuscodeWhen
400bad_requestA parameter or a field is malformed; the message names it.
401unauthorizedNo key, a malformed or unknown key, a rotated key, a paused or deleted account.
404not_foundNo such route, or no such record in this account.
405method_not_allowedA method the route does not take. Allow lists the ones it does.
410goneA PUT of a plugin session the pilot deleted in the EFB. Stop sending it.
413payload_too_largeA PUT body over 512 KB.
415unsupported_media_typeA PUT body that is not application/json.
429rate_limitedOver the per-key limit. Retry-After says how many seconds to wait.
500internalOur fault. Quote meta.requestId.

Codes are stable; build on them. Messages are English prose for a developer and may change.

07

Paging and following changes

Collections take:

ParameterMeaning
limit1–200, default 50. A page may hold fewer when records are large — a note can carry a drawing.
cursorOpaque. Pass page.nextCursor back to get the next page; null means there is none.
updatedSinceISO 8601. Only records changed after it.

The cursor is an offset into the sorted collection, so a record written between two page requests can shift a page. To follow changes, use updatedSince with the newest updatedAt you have seen: that returns every change exactly once, whatever the order.

curl -s -H "X-API-Key: $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/aircraft?limit=100&updatedSince=2026-09-17T00:00:00Z"
08

Rate limits

  • Per key: 120 requests per minute. Above it, 429 rate_limited with Retry-After in seconds.
  • Whole API: 25 requests per second sustained, bursts of 50.
  • Weather is cached for up to five minutes per aerodrome, however often you ask; fetchedAt says when it was fetched.
09

Endpoints

Every route except GET /v1/health needs the key. Collections are paged and sorted as stated under each one.

MethodPathKeyReturns
GET/v1/healthnoService health
GET/v1/meyesThe account and its key
GET/v1/aerodromesyesAerodrome cards (paged)
GET/v1/aerodromes/{id}yesOne card, by id or by identifier
GET/v1/aerodromes/{id}/attachments/{attachmentId}yesDownload link for a card attachment
GET/v1/aircraftyesAircraft (paged)
GET/v1/aircraft/{id}yesOne aircraft
GET/v1/aircraft/{id}/attachments/{attachmentId}yesDownload link for an aircraft document
GET/v1/weatheryesMETAR and TAF at every aerodrome of the pilot
GET/v1/weather/{ident}yesMETAR and TAF at one aerodrome
GET/v1/notesyesNotes (paged)
GET/v1/notes/{id}yesOne note
GET/v1/checklistsyesChecklists (paged)
GET/v1/checklists/{id}yesOne checklist
GET/v1/check-flightsyesFlight checks (paged)
GET/v1/check-flights/{id}yesOne flight
GET/v1/flight-plansyesFlight plans (paged)
GET/v1/flight-plans/{id}yesOne flight plan
GET/v1/planningsyesPlannings (paged)
GET/v1/plannings/{id}yesOne planning
GET/v1/logbookyesLogbook entries (paged)
GET/v1/logbook/totalsyesSummed hours, in minutes
GET/v1/logbook/{id}yesOne entry
GET/v1/pilotyesThe pilot profile
PUT/v1/plugin/sessions/{id}yesStore a RogerATC AI session — the only write
PUT/v1/plugin/liveyesReport live state and collect waiting instructions
GET/v1/plugin/sessionsyesRogerATC AI sessions (paged)
GET/v1/plugin/sessions/{id}yesOne RogerATC AI session
GET/v1/plugin/statsyesTotals over every RogerATC AI session

Health

  • GET/v1/healthService health no key

No key, no data, not counted in the usage statistics. For uptime monitors.

curl -s https://api.rogeratc.app/v1/health
{ "data": { "ok": true, "version": "1.0.0" }, "meta": { … } }

Account

  • GET/v1/meThe account and its key

Which account a key belongs to. The key itself is never returned — only its prefix and its dates.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/me
{
  "data": {
    "userId": "44080428-f051-7019-a08f-286b3c97b052",
    "email": "…",
    "displayName": "Martín",
    "accountCreatedAt": "2026-09-08T18:15:14.171Z",
    "apiKey": {
      "prefix": "rat_live_x3V9",
      "createdAt": "2026-09-17T15:00:00.000Z",
      "rotatedAt": null
    }
  },
  "meta": { … }
}

Aerodrome cards

  • GET/v1/aerodromesAerodrome cards (paged)
  • GET/v1/aerodromes/{id}One card, by id or by identifier
  • GET/v1/aerodromes/{id}/attachments/{attachmentId}Download link for a card attachment

The pilot's aerodrome cards: frequencies, contacts, VOR/DME, runways, taxiways, position, elevation, notes, and the NOTAM and MADHEL text the pilot copied onto the card. Sorted by code.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
{id}The card id or the aerodrome identifier, case-insensitive: /v1/aerodromes/SADF, /v1/aerodromes/len.

An identifier is the ICAO location indicator where the aerodrome has one, and the national three-letter code where it does not — as with most Argentine strips, such as LEN (Escobar) or SNY (San Nicolás).

taxiways is a list of { id, designator, surface, widthM, lighted, notes } typed by the pilot from the aerodrome chart, never pre-filled. An empty or absent list means none were entered on the card; it says nothing about the aerodrome itself.

These are the pilot's own cards, reference data typed from a chart. They are not the AIP.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/aerodromes/SADF
{
  "data": {
    "id": "7f0c…", "code": "SADF", "name": "San Fernando", "elevationFt": 10,
    "frequencies": { "tower": "119.000", "ground": "121.850", "atis": null, "…": "…" },
    "contacts": { "aroAisPhone": "…", "…": "…" },
    "runways": [
      { "designator": "05/23", "lengthM": 1690, "widthM": 30, "surface": "ASP", "…": "…" }
    ],
    "taxiways": [
      { "id": "…", "designator": "…", "surface": "…", "widthM": null,
        "lighted": null, "notes": "…" }
    ],
    "notam": "", "madhel": "", "notes": "",
    "createdAt": "…", "updatedAt": "…"
  },
  "meta": { … }
}

Attachments

attachments lists the diagrams, entry and exit procedures and photos the pilot attached: { id, name, contentType, bytes, kind, createdAt }, with kind diagram, procedures, photo or other. The card carries only this metadata; the files are in private storage. GET /v1/aerodromes/{id}/attachments/{attachmentId} returns one with a presigned HTTPS URL valid for 15 minutes: fetch it directly and without the key. 404 when the card does not list that id or the file has not been uploaded yet — an attachment added with no signal is uploaded at the next sync.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/aerodromes/SADF/attachments/0b7e…"
{
  "data": {
    "id": "0b7e…", "name": "SADF diagrama.pdf", "contentType": "application/pdf",
    "bytes": 812345, "kind": "diagram", "createdAt": "2026-09-18T12:00:00.000Z",
    "url": "https://…/u/…?X-Amz-Signature=…",
    "urlExpiresAt": "2026-09-18T12:15:00.000Z"
  },
  "meta": { … }
}

Aircraft

  • GET/v1/aircraftAircraft (paged)
  • GET/v1/aircraft/{id}One aircraft
  • GET/v1/aircraft/{id}/attachments/{attachmentId}Download link for an aircraft document

Every aircraft profile with every field — registration, ICAO type, equipment and surveillance codes, speeds, fuel, weights, arms and the envelope. Sorted by registration.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" "https://api.rogeratc.app/v1/aircraft?limit=20"

Documents

attachments lists the aircraft's flight manual, documents and photos, in the same shape as a card's, with kind manual, document, photo or other. GET /v1/aircraft/{id}/attachments/{attachmentId} works exactly like the aerodrome one: a presigned URL for 15 minutes, fetched without the key, and 404 when it does not exist or has not been uploaded yet. Files may be up to 100 MB.

Weather

  • GET/v1/weatherMETAR and TAF at every aerodrome of the pilot
  • GET/v1/weather/{ident}METAR and TAF at one aerodrome

The latest METAR and TAF, raw and decoded, for every aerodrome the pilot has: each aerodrome card and each aerodrome on the weather briefing. sources says which (aerodromeCard, briefing). /v1/weather/{ident} answers for any identifier, on the pilot's lists or not.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/weather
{
  "data": [
    {
      "ident": "SAEZ", "name": "Ezeiza", "sources": ["aerodromeCard", "briefing"],
      "status": "ok",
      "metar": {
        "raw": "METAR SAEZ 171400Z 35009KT CAVOK 21/10 Q1023 NOSIG",
        "observedAt": "2026-09-17T14:00:00.000Z",
        "ageMinutes": 12,
        "flightCategory": "VFR",
        "decoded": { "wind": { … }, "ceilingAglFt": null, "unparsed": [], "…": "…" }
      },
      "taf": {
        "raw": "TAF SAEZ 171100Z 1712/1812 …",
        "issuedAt": "…", "validFrom": "…", "validTo": "…",
        "decoded": { … }
      },
      "provider": "NOAA Aviation Weather Center (aviationweather.gov)",
      "fetchedAt": "2026-09-17T14:12:03.000Z"
    },
    {
      "ident": "LEN", "name": "Escobar", "sources": ["aerodromeCard"],
      "status": "no_icao_indicator",
      "metar": null, "taf": null, "provider": "…", "fetchedAt": null
    }
  ],
  "meta": { … }
}
statusMeaning
okA METAR, a TAF, or both. Each is dated; nothing is dropped for being old.
no_reportNOAA was asked and has nothing for this indicator.
no_icao_indicatorThe aerodrome is keyed on a national code; there is no station to ask.
unavailableNOAA could not be reached. Not the same as no weather.

Why nothing is invented

decoded is the EFB's own decoder output, so the API and the screen cannot disagree about a ceiling. Groups the decoder does not understand are listed in unparsed, never guessed.

A report is never dropped for being old: each carries its time and ageMinutes, and whether it is too old is your decision. And the four statuses are kept apart on purpose — an aerodrome with no station to ask, and a provider that could not be reached, are not an aerodrome with no weather. provider names the source on every item.

Notes

  • GET/v1/notesNotes (paged)
  • GET/v1/notes/{id}One note

The kneeboard: every note with every field, including a handwritten note's image as a PNG data URL (imageDataUrl). Newest change first. Nothing in a note is parsed.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/notes

Checklists

  • GET/v1/checklistsChecklists (paged)
  • GET/v1/checklists/{id}One checklist

Every checklist the pilot built from their aircraft's documentation, with its sections and items. Sorted by aircraft, then by the pilot's own order. The EFB ships no checklist content: every one of these was written by the pilot.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
aircraftIdOnly the checklists of this aircraft — an id from GET /v1/aircraft.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/checklists?aircraftId=ac-1"

Flight checks

  • GET/v1/check-flightsFlight checks (paged)
  • GET/v1/check-flights/{id}One flight

One record per flight an aircraft's checklists were run on: startedAt, completedAt (absent while the flight is still open — many lists are run in the air), lists in that day's order, and summary: { total, done, missing: [{ checklist, section, challenge, critical }] }. The summary is taken as the flight goes, so it still says what was left unticked after a list is edited or deleted. autoClosed: true marks a flight the app closed rather than the pilot: one left open for more than twelve hours is closed at its own last activity, never at the moment it was noticed.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and change tracking.
aircraftIdOnly that aircraft's flights — an id from GET /v1/aircraft.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/check-flights?aircraftId=ac-1"

Flight plans

  • GET/v1/flight-plansFlight plans (paged)
  • GET/v1/flight-plans/{id}One flight plan

Every ICAO flight plan with every field — items 7 to 19, the route points, the supplementary information and the filing address. Newest departureDate first. A plan made from a planning carries planningId.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/flight-plans

Plannings

  • GET/v1/planningsPlannings (paged)
  • GET/v1/plannings/{id}One planning

Every route the pilot planned on the map: the points in order with their positions, the aircraft, the planned departure, the altitude, fuel on board, the reserve and the wind choice, and the flight plan it became (flightPlanId). departure, destination and waypoints mirror points. Newest change first.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/plannings

Logbook

  • GET/v1/logbookLogbook entries (paged)
  • GET/v1/logbook/totalsSummed hours, in minutes
  • GET/v1/logbook/{id}One entry

Every flight in the logbook. The ANAC logbook line (RAAC 61.120) is under log: the eight flight-time cells, the discriminación, simulator time, landings, approaches and the purpose code. Newest log.date first.

Durations are whole minutes — never decimal hours, never hh:mm. 90 is an hour and a half; format it however your service needs.

ParameterMeaning
limit · cursor · updatedSincePaging — see Paging and following changes.
fromTotals only. First log.date counted, YYYY-MM-DD, inclusive. Optional.
toTotals only. Last log.date counted, inclusive. Optional.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" "https://api.rogeratc.app/v1/logbook?limit=100"

Totals

curl -s -H "Authorization: Bearer $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/logbook/totals?from=2026-01-01&to=2026-12-31"
{
  "data": {
    "unit": "minutes", "from": "2026-01-01", "to": "2026-12-31",
    "totals": {
      "flight": 5130, "pilot": 5130, "copilot": 0, "day": 4800, "night": 330,
      "local": 1200, "crossCountry": 3930, "instructorGiven": 0,
      "instrumentReal": 0, "instrumentHood": 60, "multiEngine": 0,
      "simulator": 120, "landings": 61, "entries": 38
    },
    "logged": {
      "unit": "tenths", "total": 856, "crossCountry": 656, "local": 200,
      "pilot": 856, "copilot": 0, "day": 801, "night": 55, "simulator": 20,
      "instructorGiven": 0, "instrumentReal": 0, "instrumentHood": 10,
      "multiEngine": 0, "landings": 61, "entries": 38
    }
  },
  "meta": { … }
}

data.logged holds the figures the Logbook screen shows: each flight's decimal hours — its cells rounded to the nearest tenth (1:50 is 1.8), unless the pilot wrote their own figure (loggedTenths on the line) — summed so that crossCountry + local, pilot + copilot and day + night always equal total. In tenths of an hour ("unit": "tenths", integers: 18 is 1.8 h). simulator is separate and not part of the total. data.totals, in minutes, is unchanged.

Computed by the same function as the EFB's Logbook screen, so the two cannot disagree. The discriminación cells are a breakdown of hours already counted in the flight-time cells and are never added to flight; simulator time is not flight time. A flight with no logbook line is not counted — entries says how many were — and a missing cell counts as zero.

Pilot profile

  • GET/v1/pilotThe pilot profile

Name, telephone, licences, ratings and their expiry, and the medical certificate — or data: null when the pilot has not created a profile. The same profile the EFB shows, and the most sensitive thing the key can read.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/pilot
10

RogerATC AI plugin

RogerATC AI, the voice ATC plugin for X-Plane 12, can keep its session history in the pilot's Copilot EFB account. When the pilot turns on EFB sync in the plugin — it is off by default, with a choice between statistics only and statistics with the event log — the plugin sends a summary of each simulator session, authenticated with the pilot's own key. The EFB stores it in the account, so it appears on every device the pilot is signed in on, and serves it back here.

With the same key the plugin reads what the pilot already keeps in the EFB, through the endpoints above: the fleet, aerodrome cards with their frequencies, runways and taxiways, checklists and flight plans.

RogerATC AI is in development and has not been released. This section is the contract its EFB sync is built against.

Store a session

  • PUT/v1/plugin/sessions/{id}Store a RogerATC AI session — the only write

An idempotent upsert of one whole session. Send it at the start, at a checkpoint every few minutes while something changed, and once at the end — always the whole session, always under the same id. A retry after a dropped connection is harmless.

ParameterMeaning
{id}The plugin's session id, stable for the flight: 3 to 128 characters of A–Z a–z 0–9 . _ -, starting with a letter or a digit.
Request
PUT /v1/plugin/sessions/5f0c2a4e-7d1b-4c6a-9d0e-2b8f1c3a6e77 HTTP/1.1
Host: api.rogeratc.app
Authorization: Bearer rat_live_…
Content-Type: application/json
Body
{
  "schema": 1,
  "status": "completed",
  "startedAt": "2026-09-17T18:02:11Z",
  "endedAt": "2026-09-17T19:14:40Z",
  "pluginVersion": "0.4.0",
  "protocolVersion": "1",
  "simulator": "X-Plane 12.1.4",
  "language": "es",
  "region": "AR",
  "dataSource": "both",
  "aircraft": { "registration": "LV-XXX", "icaoType": "C172" },
  "departure": "SADF",
  "arrival": "SAAR",
  "aerodromesVisited": ["SADF", "SAAR"],
  "stats": {
    "transmissions": 42, "fastPath": 30, "modelCalls": 12, "sayAgain": 2,
    "readbacksCorrect": 17, "readbacksIncorrect": 1, "positionCorrections": 0,
    "unprompted": 4, "emergencies": 0, "instructorQuestions": 3,
    "meanResponseMs": 1850, "p95ResponseMs": 3400,
    "byStation": { "ground": 9, "tower": 14, "approach": 11, "centre": 8 },
    "byIntent": { "request_taxi": 2, "ready_departure": 1, "position_report": 6 }
  },
  "events": [
    {
      "at": "2026-09-17T18:04:02Z", "kind": "turn",
      "station": "San Fernando Superficie", "intent": "request_taxi", "template": "taxi-clearance",
      "heard": "San Fernando superficie, LV-XXX en plataforma, solicito rodaje",
      "said": "LV-XXX, ruede a punto de espera pista 05, QNH 1018"
    }
  ]
}

Body fields

FieldRequiredRules
schemayes1.
startedAtyesISO 8601. Not more than a day in the future.
endedAtnoISO 8601, or null while flying.
statusnoin_progress, completed or aborted. Absent: completed when endedAt is set, otherwise in_progress.
pluginVersion, protocolVersion, simulator, language, regionnoShort strings, trimmed. language and region have their case normalised.
dataSourcenosim, both or efb — where the plugin took aerodrome data from. Anything else is stored as null.
aircraftno{ registration, icaoType }, upper-cased.
departure, arrivalnoAn aerodrome identifier: 3–4 of A–Z 0–9 (ICAO, or a national code where there is none). Anything else is stored as null.
aerodromesVisitednoUp to 20 identifiers, deduplicated.
statsnoThe ten counters in the example, whole and non-negative (anything else counts as 0); meanResponseMs and p95ResponseMs or null; byStation up to 30 keys and byIntent up to 60, keys matching [a-z0-9][a-z0-9_.-]{0,39}, largest kept.
eventsnoOnly with the event log enabled. Up to 200, newest kept. kind: turn, unprompted, silence, error or instructor; at required; station, intent, template; heard cut at 160 characters and said at 240. Events of an unknown kind or without a valid at are dropped.
eventsDroppednoWhat the plugin already dropped before sending; added to the server's own count.

Anything else in the body is ignored and not stored. Nothing derived from X-Plane's scenery or navigation data belongs in a session — and no audio and no provider keys.

Limits

  • The body is at most 512 KB; above it, 413.
  • The stored session is at most 200 KB: the oldest events are removed until it fits, and eventsDropped counts them.
  • One record per session — never one per radio call.

Responses

StatusBodyMeaning
201{ id, status, receivedAt, eventsStored, eventsDropped }The first time this id was stored.
200the sameThe stored copy was replaced. createdAt is kept from the first arrival.
400errorA field broke a rule above; the message names it.
410error goneThe pilot deleted this session in the EFB. Stop sending it — it will never be stored again.
413 · 415errorToo large, or not JSON.
401 · 429errorThe key, or the rate limit. A PUT every few minutes is far inside it.
curl -s -X PUT \
  -H "Authorization: Bearer $ROGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data @session.json \
  https://api.rogeratc.app/v1/plugin/sessions/5f0c2a4e-7d1b-4c6a-9d0e-2b8f1c3a6e77
HTTP/1.1 201 Created

{
  "data": {
    "id": "5f0c2a4e-7d1b-4c6a-9d0e-2b8f1c3a6e77",
    "status": "completed",
    "receivedAt": "2026-09-17T19:14:42.018Z",
    "eventsStored": 1,
    "eventsDropped": 0
  },
  "meta": { … }
}

The plugin's side of the contract

  • The simulator never waits on the API. Requests leave from a queue on disk, in the background, with a short timeout and back-off, off the audio and simulator threads. A failure, a timeout or no network is silence — never a stalled simulator.
  • 401 means the key was rotated or the account paused: stop sending, and tell the pilot once.
  • 410 means the pilot deleted that session: drop it from the queue and never send it again.
  • 429 means wait for Retry-After before the next attempt.

Live link

  • PUT/v1/plugin/liveReport live state and collect waiting instructions

While the pilot flies with the live link on in the plugin (efb_live = on, off by default), the plugin calls this route every few seconds with its measured state — position, altitude, speeds, heading, on the ground or airborne, COM1, transponder, QNH, wind — and with the outcome of every instruction it dealt with. The answer carries the instructions an instructor left on the EFB's RogerATC console, and how long to wait before calling again (pollMs).

It is not a synced record: the state is overwritten on every call and the server forgets it after ten minutes. A track is never kept. Navaids, frequencies and runways do not travel: they come from the simulator's navigation data, whose licence forbids them leaving the pilot's machine, and the EFB computes them from its own public database.

Each instruction is handed over once. One the plugin has not collected within two minutes expires and is never delivered late. The plugin answers in its next report with spoken and the exact words the controller said, or refused and a reason (on_ground, unknown_navaid, no_dme…). The plugin's engine writes every word; the EFB only asks.

Request
curl -X PUT https://api.rogeratc.app/v1/plugin/live \
  -H "Authorization: Bearer $COPILOT_EFB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "schema": 1, "sessionId": "2026-10-03_1415", "sentAt": "2026-10-03T14:15:02Z",
        "aircraft": { "registration": "LV-XXX", "icaoType": "C172" },
        "sim": { "paused": false, "phase": "cruise", "onGround": false,
                 "latDeg": -34.6, "lonDeg": -58.7, "altitudeFt": 2500, "headingMagDeg": 270,
                 "groundspeedKt": 95, "verticalSpeedFpm": 0, "…": "…" },
        "aerodrome": { "ident": "SADF", "activeRunway": "05" },
        "transcript": [], "acks": [] }'
Response
{
  "data": {
    "commands": [
      { "id": "1759500902000-x7Kq2m9A", "createdAt": "2026-10-03T14:15:00Z",
        "request": { "kind": "holding", "navaid": "SFD", "inboundCourseDeg": 50,
                     "turn": "right", "legMinutes": null, "altitudeFt": 2500 } }
    ],
    "pollMs": 2000
  }
}

Read sessions

  • GET/v1/plugin/sessionsRogerATC AI sessions (paged)
  • GET/v1/plugin/sessions/{id}One RogerATC AI session

Every stored session, newest startedAt first, as stored: the body fields above plus id, source (rogeratc-plugin), durationMinutes (start to end, or to the last arrival while in progress), eventsDropped, receivedAt, createdAt and updatedAt.

ParameterMeaning
limit · cursor · updatedSincePaging. updatedSince is the way to follow sessions still in progress.
curl -s -H "Authorization: Bearer $ROGER_API_KEY" \
  "https://api.rogeratc.app/v1/plugin/sessions?updatedSince=2026-09-17T18:00:00Z"

Totals

  • GET/v1/plugin/statsTotals over every RogerATC AI session

The same totals the EFB's RogerATC AI screen shows. minutes is the sum of every session's durationMinutes. meanResponseMs is weighted by each session's transmissions, and null when nothing was timed.

curl -s -H "Authorization: Bearer $ROGER_API_KEY" https://api.rogeratc.app/v1/plugin/stats
{
  "data": {
    "sessions": 12,
    "byStatus": { "in_progress": 0, "completed": 11, "aborted": 1 },
    "minutes": 842, "transmissions": 506,
    "readbacksCorrect": 201, "readbacksIncorrect": 9,
    "sayAgain": 14, "emergencies": 1, "instructorQuestions": 37,
    "meanResponseMs": 1910,
    "firstStartedAt": "…", "lastStartedAt": "…",
    "byStation": { "tower": 180, "ground": 120, "approach": 110, "centre": 96 },
    "aerodromes": [{ "ident": "SADF", "sessions": 9 }],
    "aircraft": [{ "label": "LV-XXX", "sessions": 10 }]
  },
  "meta": { … }
}
11

Versions and changes

Paths carry the major version. New routes, new fields and new status values are additive and stay in /v1, so a client should ignore what it does not recognise. Anything that would break a client that follows this page goes to a new major version, and /v1 stays until its users have moved.

VersionDateChange
1.11.02026-10-03GET /v1/plannings and GET /v1/plannings/{id}: the routes planned on the map. A flight plan made from a planning carries planningId. Additive.
1.10.02026-10-03PUT /v1/plugin/live: the RogerATC AI live link — the simulator's measured state, and the instructor's instructions. The API's second write, with state that expires and never syncs. Additive.
1.9.02026-09-24/v1/logbook/totals adds data.logged, the decimal hours in tenths that the Logbook screen shows. Lines may carry loggedTenths. Additive.
1.8.02026-09-22Flight plans carry signatureDataUrl, that plan's signature. Flight checks carry autoClosed. Additive.
1.7.02026-09-19Aircraft carry attachments — manuals, documents and photos — and GET /v1/aircraft/{id}/attachments/{attachmentId} returns a short-lived download link. Per-file limit: 100 MB. Additive.
1.6.02026-09-18Flight checks: GET /v1/check-flights and GET /v1/check-flights/{id}. Additive.
1.5.02026-09-18Aerodrome cards carry aids (radio and visual aids) and fuel.products (oils and additives). Additive.
1.4.02026-09-18Aerodrome cards carry attachments, and GET /v1/aerodromes/{id}/attachments/{attachmentId} returns a short-lived download link. Additive.
1.3.12026-09-17Security pass: least privilege for the API, a daily quota of new plugin sessions, and an account reset rotates its key.
1.3.02026-09-17Aerodrome cards carry fuel. Additive.
1.2.02026-09-17Checklists: GET /v1/checklists and GET /v1/checklists/{id}. RogerATC AI plugin sessions: PUT /v1/plugin/sessions/{id} (the first write), GET /v1/plugin/sessions, GET /v1/plugin/sessions/{id} and GET /v1/plugin/stats. New error codes gone, payload_too_large and unsupported_media_type. 405 lists the allowed methods.
1.1.02026-09-17Aerodrome cards carry taxiways. Additive: no route and no existing field changed.
1.0.02026-09-17First release: the personal key, and read-only routes for aerodromes, aircraft, weather, notes, flight plans, the logbook and its totals, and the pilot profile.