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.
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 logfuelLog— fuel uplift / fuel-on-board historynotes— free-text notespostFlightReport— 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.
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.payloadand storecurrent.version. -
Override — re-send
flightDataSavewithexpectedVersion = current.versionandlastDownloadedAt = 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".
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Required | Must be "flightDataList" |
data.ofpId |
string | Required | OFP identifier (from ofpList) |
Example
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
{
"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.
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
{
"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.
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 -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 -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
{
"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)
{
"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.
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
{
"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.
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)
{
"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.
|