Flight Data API

Free-form, server-side persistent storage attached to an imported OFP. Use it to back the journey log, fuel log, post-flight report, free notes — anything the client wants to keep across sessions and devices, with built-in optimistic locking to avoid lost updates.

Overview

Every entry is uniquely keyed by (userId, ofpId, dataKey). The payload is opaque from the server's perspective — the backend never inspects it, so you can evolve your schema entirely in the client. Each row carries a monotonic version and updatedAt timestamp so concurrent edits can be detected and surfaced to the user.

When to use this

Use Flight Data when you want pilot-filled information about a flight to survive a tablet crash, a logout, or be picked up by a second device. The OFP itself is delivered by ofpImport / ofpGet; Flight Data is the layer where the pilot's own entries live.

Concepts & Conflict Strategy

The dataKey

A short client-defined section name (max 64 chars, allowed: A-Z a-z 0-9 . _ -). One OFP can carry as many keys as you need — for example:

  • journeyLog — pilot's filled journey log
  • fuelLog — fuel uplift / fuel-on-board history
  • notes — free-text notes
  • postFlightReport — post-flight write-up

If you omit dataKey, the key "default" is used.

Optimistic locking

flightDataSave accepts two markers describing what the client believes is the latest state on the server:

Marker Source Behaviour
expectedVersion Integer, last version the client received Strict check. If a row exists and the value differs from the server's current version, the save is rejected with 409 VERSION_CONFLICT.
lastDownloadedAt ISO 8601 timestamp, last updatedAt the client saw Secondary check. If the server's current updatedAt is more recent, the save is rejected with the same 409 VERSION_CONFLICT. Useful for the human-friendly conflict message even when the version cache is stale on the client.

Both fields are optional. Sending neither effectively asks for last-write-wins; sending both is the recommended belt-and- suspenders approach.

Recommended client UX for VERSION_CONFLICT

When the server returns 409, the response includes a current object with the up-to-date payload and metadata (who saved it and when). Present the user with three choices:

  • Cancel — keep the local edits unsaved; nothing is sent.
  • Download latest — replace the local copy with current.payload and store current.version.
  • Override — re-send flightDataSave with expectedVersion = current.version and lastDownloadedAt = current.updatedAt. The server's copy is replaced.

Authorization scope

Today only the OFP owner — the user who originally imported it — can read or write its Flight Data. Cross-account sharing (sharing a flight with another pilot) is delivered by the upcoming OFP Sharing endpoints; once enabled, Flight Data access will automatically follow the share's permission level.

flightDataList

List every saved dataKey attached to a given OFP, including the current version, timestamps and payload size. Useful for the client to detect "there is a newer version of this section on the server".

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "flightDataList"
data.ofpId string Required OFP identifier (from ofpList)

Example

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "flightDataList",
    "data": { "ofpId": "12345_4" }
  }'

Response

JSON
{
  "success": true,
  "ofpId": "12345_4",
  "count": 2,
  "items": [
    {
      "dataKey": "journeyLog",
      "version": 9,
      "createdAt": "2026-05-27 18:02:11",
      "updatedAt": "2026-05-27 19:31:08",
      "createdByUserId": 9576,
      "updatedByUserId": 9576,
      "payloadBytes": 4821
    },
    {
      "dataKey": "fuelLog",
      "version": 2,
      "createdAt": "2026-05-27 18:14:00",
      "updatedAt": "2026-05-27 18:47:33",
      "createdByUserId": 9576,
      "updatedByUserId": 9576,
      "payloadBytes": 312
    }
  ]
}

flightDataGet

Fetch the full payload for one (ofpId, dataKey). Returns the raw payload (decoded from JSON if possible) plus version metadata.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "flightDataGet"
data.ofpId string Required OFP identifier
data.dataKey string Optional Section key. Defaults to "default".

Response

JSON
{
  "success": true,
  "data": {
    "ofpId": "12345_4",
    "dataKey": "journeyLog",
    "payload": { "...": "client-defined object" },
    "version": 9,
    "createdAt": "2026-05-27 18:02:11",
    "updatedAt": "2026-05-27 19:31:08",
    "createdByUserId": 9576,
    "updatedByUserId": 9576
  }
}

flightDataSave

Create or update one (ofpId, dataKey). Returns the newly saved row including its bumped version and server-side updatedAt. On conflict, returns HTTP 409 with the server's current copy.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "flightDataSave"
data.ofpId string Required OFP identifier
data.dataKey string Optional Defaults to "default".
data.payload object | array | string Required Opaque payload. Objects/arrays are JSON-encoded server-side. Strings are stored verbatim. Hard cap: 4 MB.
data.expectedVersion integer | null Recommended The version the client believes is current. Send null or 0 if creating the row for the first time.
data.lastDownloadedAt string (ISO 8601) Recommended The updatedAt the client received last time it fetched this row.

Example — first save

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "flightDataSave",
    "data": {
      "ofpId": "12345_4",
      "dataKey": "journeyLog",
      "payload": { "blockOut": "1832Z", "takeoff": "1844Z" },
      "expectedVersion": 0
    }
  }'

Example — subsequent save

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "flightDataSave",
    "data": {
      "ofpId": "12345_4",
      "dataKey": "journeyLog",
      "payload": { "blockOut": "1832Z", "takeoff": "1844Z", "landing": "2014Z" },
      "expectedVersion": 1,
      "lastDownloadedAt": "2026-05-27T19:14:22Z"
    }
  }'

Success Response

JSON
{
  "success": true,
  "data": {
    "ofpId": "12345_4",
    "dataKey": "journeyLog",
    "payload": { "...": "echoed back" },
    "version": 2,
    "createdAt": "2026-05-27 18:02:11",
    "updatedAt": "2026-05-27 19:36:54",
    "createdByUserId": 9576,
    "updatedByUserId": 9576
  }
}

Conflict Response (HTTP 409)

JSON
{
  "success": false,
  "error": {
    "message": "A newer version exists on the server.",
    "code": "VERSION_CONFLICT",
    "status": 409
  },
  "current": {
    "ofpId": "12345_4",
    "dataKey": "journeyLog",
    "payload": { "...": "server's current copy" },
    "version": 9,
    "createdAt": "2026-05-27 18:02:11",
    "updatedAt": "2026-05-27 19:31:08",
    "createdByUserId": 9576,
    "updatedByUserId": 9576
  }
}

flightDataDelete

Remove one (ofpId, dataKey). Returns 404 if no such row exists.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "flightDataDelete"
data.ofpId string Required OFP identifier
data.dataKey string Optional Defaults to "default".

Response

JSON
{
  "success": true,
  "ofpId": "12345_4",
  "dataKey": "journeyLog",
  "deleted": true
}

flightDataHistory

Read the append-only audit log of every change made through flightDataSave and flightDataDelete for one (ofpId, dataKey). Useful for showing “who changed what” in shared flights and for building an “undo last change” affordance on the client. Requires view access.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "flightDataHistory"
data.ofpId string Required OFP identifier
data.dataKey string Optional Defaults to "default".
data.limit integer Optional Max rows. Default 20, capped at 100.
data.includePayload boolean Optional When true, includes the full payload snapshot per row. Default false (metadata only).

Response (metadata only)

JSON
{
  "success": true,
  "ofpId": "12345_4",
  "dataKey": "journeyLog",
  "count": 3,
  "items": [
    { "id": 314, "version": 9, "changeType": "update", "payloadBytes": 4821,
      "changedAt": "2026-05-27 19:31:08", "changedByUserId": 11203 },
    { "id": 290, "version": 8, "changeType": "update", "payloadBytes": 4117,
      "changedAt": "2026-05-27 19:14:22", "changedByUserId": 9576  },
    { "id": 117, "version": 1, "changeType": "insert", "payloadBytes": 312,
      "changedAt": "2026-05-27 18:02:11", "changedByUserId": 9576  }
  ]
}

Error codes

Code HTTP Description
AUTH_REQUIRED 401 JWT missing or invalid.
INVALID_OFP_ID 400 OFP id missing or contains forbidden characters.
INVALID_DATA_KEY 400 dataKey contains forbidden characters or exceeds 64 bytes.
OFP_NOT_FOUND 404 No OFP with that id is owned by the caller.
FLIGHT_DATA_NOT_FOUND 404 No saved row exists for (ofpId, dataKey).
MISSING_PAYLOAD 400 flightDataSave body had no payload field.
INVALID_PAYLOAD 400 Payload is not an object, array or JSON string.
INVALID_TIMESTAMP 400 lastDownloadedAt could not be parsed as ISO 8601.
PAYLOAD_TOO_LARGE 413 Payload exceeds the 4 MB hard cap.
VERSION_CONFLICT 409 The client's expectedVersion / lastDownloadedAt is stale. The response body's current field contains the server's up-to-date copy.