OFP Sharing API

Cross-account flight plan sharing. Lets an OFP owner invite another VPT account — by email, by short share-code, or by QR deep-link — to view or co-edit one of their stored flights and its Flight Data.

Overview

Sharing is built on a single table, ofp_shares. Every invite gets two identifiers:

  • shareToken — a 48-character URL-safe opaque token. Used inside the QR payload and deep-link URL. Sensitive: only echoed back to the owner.
  • shareCode — an 8-character uppercase alphanumeric code (alphabet excludes look-alikes I/O/0/1) for recipients who can’t scan the QR.

A share has a status lifecycle of pending → accepted (or revoked at any time). It may carry an expiresAt after which it can no longer be redeemed even if still pending.

Permission model

Access checks for OFP reads, Flight Data reads, and Flight Data writes all go through the OfpAccessService. It returns the OWNER’s userId for every authorized caller — that’s how shared users read and write the same underlying Flight Data row.

Permission Can do
view Read the OFP (ofpGet), list/read Flight Data (flightDataList, flightDataGet).
edit Everything view can do, plus flightDataSave and flightDataDelete.
Owner-only actions

Deleting the OFP itself (ofpDelete) and creating / revoking shares stay restricted to the owner regardless of the recipient’s permission.

The server does not render the QR image — the client does, from the qrPayload string returned by ofpShare and ofpShareList. Both the qrPayload and deepLink use the vpt://share/{shareToken} scheme so the mobile app can register a single deep-link handler that routes straight to ofpShareAccept:

Deep link shapes
vpt://share/abcDEF-48charsTokenHere
https://www.virtualperformancetool.com/share?t=abcDEF-48charsTokenHere

ofpShare

Owner-only. Creates a new pending invite, returns the token, short share-code and QR payload.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "ofpShare"
data.ofpId string Required OFP identifier (caller must own it)
data.permission string Optional "view" (default) or "edit"
data.sharedWithEmail string Optional Email of the invited account. The invite is still issued if the email isn’t a registered VPT user yet — the recipient redeems it via shareCode/QR after sign-up.
data.expiresInDays integer Optional 1–365. Omit / null for no expiry.

Example

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "ofpShare",
    "data": {
      "ofpId": "12345_4",
      "permission": "edit",
      "sharedWithEmail": "copilot@example.com",
      "expiresInDays": 7
    }
  }'

Response

JSON
{
  "success": true,
  "share": {
    "id": 42,
    "ownerUserId": 9576,
    "ofpId": "12345_4",
    "sharedWithUserId": 11203,
    "sharedWithEmail": "copilot@example.com",
    "permission": "edit",
    "status": "pending",
    "shareCode": "K7H2PXQR",
    "createdAt": "2026-05-27 20:14:11",
    "acceptedAt": null,
    "revokedAt": null,
    "expiresAt": "2026-06-03 20:14:11",
    "shareToken": "v9Nq...48charsTotal...wA",
    "shareUrl": "https://www.virtualperformancetool.com/share?t=v9Nq...wA",
    "deepLink": "vpt://share/v9Nq...wA",
    "qrPayload": "vpt://share/v9Nq...wA"
  }
}

ofpShareList

List shares related to the caller. Returns two arrays: issued (shares the caller created — include the shareToken so the owner can re-render the QR) and received (shares targeted at the caller’s userId or email — never include the shareToken).

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "ofpShareList"
data.ofpId string Optional Filter to a single OFP
data.scope string Optional "all" (default), "issued", or "received"

ofpShareAccept

The recipient redeems an invite using either the shareToken (deep-link / QR path) or the human-typed shareCode. Idempotent: re-accepting an already- accepted share returns success with already: true.

POST /api

Request Body (one of)

Parameter Type Description
data.shareToken string 48-char token from the QR payload / deep link
data.shareCode string 8-char human-typed code (case-insensitive)

Example

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

ofpShareRevoke

Either the owner or the current recipient can revoke. The row is kept with status = 'revoked' for audit. Idempotent.

POST /api

Request Body

Parameter Type Required Description
query string Required Must be "ofpShareRevoke"
data.id integer Required Share id from ofpShareList

Error codes

Code HTTP Description
AUTH_REQUIRED 401 JWT missing or invalid.
INVALID_OFP_ID 400 OFP id missing or contains forbidden characters.
OFP_NOT_FOUND 404 Caller does not own this OFP (used by ofpShare).
INVALID_PERMISSION 400 permission is not "view" or "edit".
INVALID_EMAIL 400 sharedWithEmail is not a valid email.
INVALID_EXPIRY 400 expiresInDays outside 1–365.
INVALID_SHARE_TARGET 400 Cannot share with yourself / accept your own share.
SHARE_NOT_FOUND 404 No share matches the supplied token / code / id.
SHARE_REVOKED 410 Share was previously revoked.
SHARE_EXPIRED 410 Share is past its expiresAt.
SHARE_FORBIDDEN 403 This share is bound to a different account / caller can’t revoke it.
INSUFFICIENT_PERMISSION 403 Caller has view access but tried a Flight Data write.