User Management

Register new users and handle password resets through the API. Registration requires a developer key with FULL user_info access level.

Overview

The User Management endpoints allow applications to register new accounts, handle password recovery flows, and manage user preferences. These are public endpoints (no JWT required), but they do require a valid developer key (devKey) in the request body. The Refresh User Data endpoint requires JWT authentication.

Developer Key Permissions

The register endpoint requires a developer key with FULL user_info access level. Password reset endpoints require any valid developer key. Contact the VPT team to request FULL access for your developer key.

Registration
Create new accounts with automatic or custom crewcode, initial preferences, and JWT auto-login.
Password Reset
Secure email-based password recovery with 1-hour expiry and rate limiting.
Security
Passwords are peppered and hashed with bcrypt. Reset emails prevent enumeration attacks.

Register

Create a new user account. On success, the user is automatically logged in and a JWT token is returned, identical to the login response. A user_preferences row is also created with optional initial settings.

POST /api

Request Body

JSON
{
  "query": "register",
  "devKey": "your_dev_key",
  "data": {
    "email": "pilot@example.com",
    "password": "securePassword123",
    "firstname": "John",
    "lastname": "Doe",
    "rank": "CAPT",
    "crewcode": "DOEJ",
    "pin": "1234",
    "simbriefId": 132456,
    "userPreferences": {
      "defaultUnit": "METRIC",
      "multiplayerNetwork": "VATSIM"
    }
  }
}

Parameters

Parameter Type Required Description
devKey string Required Developer key with FULL user_info access level
data object
email string Required User's email address (must be unique)
password string Required Password (minimum 6 characters)
firstname string Required User's first name
lastname string Required User's last name
rank string Optional Pilot rank: CAPT, FO, or LTC. Defaults to CAPT
crewcode string Optional Custom crewcode. If omitted, auto-generated from name (first 5 chars of lastname + first char of firstname, uppercased)
pin string Optional PIN code used to sign export data
simbriefId integer Optional SimBrief Pilot ID (numeric). Found in your SimBrief account settings (e.g. 132456)
userPreferences object Optional Initial user preferences (see below)

userPreferences Object

Field Type Default Description
defaultUnit string METRIC Unit system: METRIC, METRIC_M, IMPERIAL, HYBRID1, HYBRID2, HYBRID3
multiplayerNetwork string None Online multiplayer network: None, IVAO, VATSIM

Success Response

200 OK
{
  "success": true,
  "apiVersion": "1.3.6",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expires": "2025-07-28T12:00:00Z",
  "expiresIn": 2592000,
  "user": {
    "email": "pilot@example.com",
    "firstname": "John",
    "lastname": "Doe",
    "crewcode": "DOEJ",
    "rank": "CAPT",
    "userPreferences": {
      "defaultUnit": "METRIC",
      "onlineServer": "VATSIM",
      "simbriefUserId": 132456,
      ...
    },
    ...
  }
}
Auto-generated Fields

If crewcode is not provided, it is automatically generated from the user's name (first 5 characters of last name + first character of first name, uppercased, accents stripped). The API key is always auto-generated in the format API-XXXX-XXXX-XXXX.

Example Request

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "register",
    "devKey": "your_dev_key",
    "data": {
      "email": "pilot@example.com",
      "password": "securePassword123",
      "firstname": "John",
      "lastname": "Doe",
      "rank": "CAPT",
      "pin": "1234",
      "simbriefId": 132456,
      "userPreferences": {
        "defaultUnit": "METRIC",
        "multiplayerNetwork": "VATSIM"
      }
    }
  }'

Request Password Reset

Send a password reset email to the user. The email contains a link with a reset token valid for 1 hour. Rate limited to one request per email per 15 minutes.

POST /api

Request Body

JSON
{
  "query": "requestPasswordReset",
  "devKey": "your_dev_key",
  "data": {
    "email": "pilot@example.com",
    "returnUrl": "vpt://password-reset-success"
  }
}

Parameters

Parameter Type Required Description
devKey string Required Any valid developer key
data.email string Required Email address of the account to reset
data.returnUrl string Optional URL to redirect to after successful password reset. Supports custom schemes (e.g. vpt://password-reset-success). The URL is securely embedded in the encrypted reset token.

Success Response

200 OK
{
  "success": true,
  "message": "If an account with this email exists, a password reset link has been sent."
}
Security Note

This endpoint always returns a success response regardless of whether the email exists. This prevents email enumeration attacks. The reset email is only sent if the account exists.

Reset Password

Complete the password reset using the token from the reset email. The token is valid for 1 hour from when the reset was requested.

POST /api

Request Body

JSON
{
  "query": "resetPassword",
  "devKey": "your_dev_key",
  "data": {
    "token": "cGlsb3RAZXhhbXBsZS5jb20=.MTcxOTAwMDAwMA==",
    "password": "newSecurePassword456"
  }
}

Parameters

Parameter Type Required Description
devKey string Required Any valid developer key
data.token string Required Reset token received via email
data.password string Required New password (minimum 6 characters)

Success Response

200 OK
{
  "success": true,
  "message": "Password has been reset successfully. You can now log in with your new password.",
  "returnUrl": "vpt://password-reset-success"
}
Return URL Redirect

If a returnUrl was provided in the initial requestPasswordReset call, it is securely embedded in the encrypted token and returned in the success response as returnUrl. When the user resets their password via the web form, they are automatically redirected to this URL after 2 seconds. This is useful for deep-linking back into a mobile app (e.g. vpt://password-reset-success).

Password Reset Flow

1
Request Reset

Client calls requestPasswordReset with user's email

→
2
Email Sent

Server sends reset email with a tokenized link (1 hour expiry)

→
3
Reset Password

Client calls resetPassword with the token and new password

→
4
Redirect / Login

If a returnUrl was provided, the user is redirected to it automatically. Otherwise, they can login with their new password via the login endpoint.

Refresh User Data

Retrieve the full user object — profile, preferences, subscription state, and unit configuration — without re-authenticating. Returns the same payload as login, minus a new JWT token. Use this to sync user state after preferences change or when resuming from the background.

Backward Compatibility

The legacy query name refreshUserPreferences still works and is an exact alias for refreshUserData.

POST /api
Authentication Required

This endpoint requires a valid JWT token in the Authorization header. No devKey is needed — the developer key is already embedded in the JWT.

Request Body

JSON
{
  "query": "refreshUserData"
}

Example Request

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 1.3.6" \
  -d '{
    "query": "refreshUserData"
  }'

Success Response

200 OK
{
  "success": true,
  "user": {
    "email": "pilot@example.com",
    "firstname": "John",
    "lastname": "Doe",
    "crewcode": "DOEJ",
    "rank": "CAPT",
    "subscription": {
      "title": "VPT Global - Pro (Yearly)",
      "level": 2,
      "status": "ACTIVE",
      "isActive": true,
      "isExpired": false,
      "expiry": "2099-12-30T00:00:00+01:00",
      "daysRemaining": 26910,
      "capabilities": {
        "isNotam": true,
        "isNnc": true,
        "isMel": true,
        "isCdl": true,
        "isMostFav": true,
        "isBalkedLanding": true,
        "isAllIntersections": true,
        "isCommercial": true,
        "isFleetDatabase": true,
        "maxCalculation": 600
      },
      "preview": { "active": false, "reason": null, "allowedAirports": [], "message": null }
    },
    "ranks": ["LTC", "CAPT", "FO", "JFO"],
    "vaPartners": [
      {
        "id": 1, "name": "VPT Airlines", "website": "https://...", "contactEmail": "contact@vptairlines.com",
        "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
      }
    ],
    "vaMemberships": [
      {
        "id": 1, "name": "VPT Airlines", "website": "https://...", "contactEmail": "contact@vptairlines.com",
        "isActive": true,
        "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
      }
    ],
    "activeVa": {
      "id": 1, "name": "VPT Airlines", "website": "https://...", "contactEmail": "contact@vptairlines.com",
      "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
    },
    "userStatisticsSinceLastLogin": { ... },
    "userPreferences": {
      "settingsLocked": false,
      "displayIntegratedTutorial": true,
      "defaultUnit": "METRIC",
      "onlineServer": "VATSIM",
      "defaultAircraftOnStartup": {
        "file": "320-212-CFM56-5A3.json",
        "userRegistration": null,
        "vaFleet": null
      },
      "performance": {
        "takeoff": {
          "customizeStandardEngineFailureProcedure": true,
          "customizedText": "Maintain [RWY_TRACK]° to 25 NM...",
          "displayEngineFailureInFull": true
        },
        "wb": { "displayGraphicalError": true }
      },
      "export": {
        "signBeforeExport": true,
        "printerPaperWidth_mm": 8,
        "hoppieAutomaticExport": false,
        "hoppieCallsign": null,
        "hoppieServer": "None",
        "hoppieFormat": "Raw"
      },
      "simbrief": {
        "simbriefUserId": 132456,
        "simbriefLoadDeviation": 15,
        "simbriefImportOption": "Simbrief"
      },
      "efb": {
        "enableEFB": true,
        "modules": { "briefingNavlog": true, "checklist": true, "library": true, "tools": true },
        "toolsOptions": null
      },
      "weather": { "display": true, "notification": true }
    }
  },
  "units": {
    "unitConfig": [ ... ],
    "unitDetails": [ ... ]
  }
}
Deprecated Fields (API 1.3.0)

The following preference fields are deprecated in API 1.3.0 and are no longer returned. They remain in the database for older API versions:

  • displayDemoAircraft
  • landing.displayRunwayExits — exits are now always drawn on landing performance graphics
  • disclaimer.* (dateOfSignature, signature, removeDisclaimer)
  • visualCustomization.* (global, layout, form, result)
  • export.exportEmail — exports are sent to users.email only
  • export.exportURL
New efb Group (API 1.3.0)

The new top-level efb object groups in-app EFB module toggles:

  • enableEFB — master switch (default true)
  • modules.briefingNavlog, modules.checklist, modules.library, modules.tools — per-module toggles (defaults true)
  • toolsOptions — opaque JSON store synchronized across this user's clients. null means "use the client defaults". When set, must be an array of objects {id, visible:bool, order:int}.
Top-level weather Group (API 1.3.0)

The new top-level weather object groups weather-related user preferences:

  • weather.display — master toggle for displaying weather in the app (default true)
  • weather.notification — opt-in to the new weather notification stream (default true)

Response Fields

Field Type Description
user object Full user profile object — identical to the user object returned by login, without a new JWT. Fields returned depend on your developer key's access level.
user.subscription object Current subscription state. See subscription object.
user.ranks string[] Allowed rank enum values: LTC, CAPT, FO, JFO.
user.vaPartners array All active VA partners the user can join. Each entry contains id, name, website, contactEmail, logoDark, logoWhite.
user.vaMemberships array VAs the authenticated user belongs to. Each entry adds isActive indicating whether it is the current active VA.
user.activeVa object | null Full details of the currently active VA, or null if none is set. Includes logoDark and logoWhite logo URLs.
user.userPreferences object The user's current preferences.
user.userStatisticsSinceLastLogin object Usage statistics accumulated since the last login.
units.unitConfig array List of available unit configurations (e.g., METRIC, IMPERIAL) with their display labels.
units.unitDetails array Detailed unit definitions for each unit system, including weight, distance, temperature, altitude, and speed units.
When to Use

Call this endpoint when your application needs to sync user state — for example, after the user changes preferences on another device, after a subscription change, or when your app resumes from the background. Returns the same data as login without issuing a new token.

Save User Settings

Update the authenticated user's preferences. API 1.3.0 uses an adaptive partial update: only fields present in the request payload are written; missing keys leave the corresponding DB columns untouched. Unknown keys are silently ignored (forward-compatible).

POST /api
Authentication Required

Requires a valid JWT token. The dispatch case saveUserSettings handles desktop and mobile alike; saveUserSettingsMobile is kept as an alias for backwards compatibility and writes the same columns.

Request Body — Minimal (single field)

JSON
{
  "query": "saveUserSettings",
  "data": {
    "defaultUnit": "IMPERIAL"
  }
}

Request Body — EFB module toggle (nested partial)

JSON
{
  "query": "saveUserSettings",
  "data": {
    "efb": {
      "modules": {
        "checklist": false
      }
    }
  }
}

Only the enableChecklist column is updated. Sibling EFB toggles (briefingNavlog, library, tools) and enableEFB itself remain untouched.

Request Body — Full payload

JSON
{
  "query": "saveUserSettings",
  "data": {
    "displayIntegratedTutorial": true,
    "defaultUnit": "METRIC",
    "onlineServer": "VATSIM",
    "defaultAircraftOnStartup": {
      "file": "320-212-CFM56-5A3.json",
      "userRegistration": "F-GKXY",
      "vaFleet": null
    },
    "performance": {
      "takeoff": {
        "customizeStandardEngineFailureProcedure": true,
        "customizedText": "Maintain [RWY_TRACK]° to 25 NM and HOLD.",
        "displayEngineFailureInFull": true
      },
      "wb": { "displayGraphicalError": true }
    },
    "export": {
      "signBeforeExport": true,
      "printerPaperWidth_mm": 8,
      "hoppieAutomaticExport": false,
      "hoppieCallsign": "AFR123",
      "hoppieServer": "VATSIM",
      "hoppieFormat": "MCDU"
    },
    "simbrief": {
      "simbriefUserId": 132456,
      "simbriefLoadDeviation": 15,
      "simbriefImportOption": "Simbrief"
    },
    "efb": {
      "enableEFB": true,
      "modules": {
        "briefingNavlog": true,
        "checklist": true,
        "library": true,
        "tools": true
      },
      "toolsOptions": [
        { "id": "wnb",     "visible": true,  "order": 0 },
        { "id": "weather", "visible": true,  "order": 1 },
        { "id": "fuel",    "visible": false, "order": 2 }
      ]
    },
    "weather": {
      "display": true,
      "notification": true
    }
  }
}

Success Response

200 OK
{
  "success": true,
  "fieldsUpdated": 1
}

Validation Errors

200 OK (with errors)
{
  "error": "Validation failed",
  "details": [
    "defaultUnit: must be one of: METRIC, METRIC_M, IMPERIAL, HYBRID1, HYBRID2, HYBRID3",
    "efb.toolsOptions: toolsOptions[2].order is required"
  ]
}
Server-enforced Settings Lock

When settingsLocked = true on the user's row, calls to saveUserSettings are rejected with "User preferences are locked by an administrator". The settingsLocked field is admin-only: any attempt to set or unset it through the user-facing endpoint is silently dropped (treated like an unknown key). Only the admin save path can toggle the lock or bypass it (used by the admin panel and by automated VA-scoped configuration scripts).

toolsOptions Schema

efb.toolsOptions is an opaque server-side store synchronized across this user's clients. Send the full array on every save:

  • null — use client defaults (no per-user customization)
  • Array — each item must be an object with id (string), visible (boolean), order (integer)

User Statistics

Retrieve usage statistics for the currently authenticated user. The response includes calculation distribution, top aircraft and airports, usage habits by weekday and hour, and 30-day allowance tracking.

POST /api
Authentication Required

This endpoint requires a valid JWT token in the Authorization header. No devKey is needed when using JWT authentication.

Request Body

JSON
{
  "query": "userStatistics"
}

Example Request

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 1.3.6" \
  -d '{
    "query": "userStatistics"
  }'

Success Response

200 OK
{
  "success": true,
  "query": "userStatistics",
  "calculations": {
    "takeoff": 192,
    "landingDispatch": 41,
    "landingEnroute": 58,
    "other": 4,
    "total": 295
  },
  "allowance30d": {
    "baseAllowed": 300,
    "extraCalculationGift": 25,
    "totalAllowed": 325,
    "used": 214,
    "remaining": 111,
    "overage": 0,
    "usagePct": 65.8,
    "windowDays": 30
  },
  "mostUsedAircraft": [
    { "profile": "A320-214 CFM", "total": 88 }
  ],
  "usageHabits": {
    "weekday": [
      { "label": "Monday", "count": 23 }
    ],
    "hourOfDay": [
      { "hour": 14, "label": "14:00", "count": 17 }
    ]
  },
  "mostUsedAirports": [
    { "icao": "LFPG", "total": 15 }
  ],
  "statistics": {
    "libraryUsedBytes": 5242880,
    "libraryAvailableMb": 100
  }
}

Response Fields

Field Type Description
calculations object Aggregated calculation counters split by usage group, plus total.
allowance30d object 30-day quota and consumption summary, including base product allowance, user extra gift, usage percentage, and remaining calculations.
mostUsedAircraft array Top aircraft profiles used by the authenticated user. Each item includes profile and total.
usageHabits.weekday array Distribution of calculations by weekday. Items include label and count.
usageHabits.hourOfDay array Distribution of calculations by hour (0-23). Items include hour, label, and count.
mostUsedAirports array Top departure airports by ICAO code for the authenticated user. Each item includes icao and total.
statistics.libraryUsedBytes integer Total bytes consumed by the user's personal library uploads. Divide by 1048576 to convert to MB.
statistics.libraryAvailableMb integer Storage quota in MB granted by the user's active subscription. 0 means no active subscription or no storage entitlement.
Notes

Statistics are computed from the authenticated user's historical usage data. If no statistics are available yet, arrays may be empty and counters will be returned as 0.

Get User Meta

Returns helper data the client needs to render profile / VA forms: allowed rank values, the list of selectable VAs (Virtual Airlines), and the user's current memberships.

POST /api?query=getUserMeta

Authentication

JWT required. Any user_info dev key level (SIMPLE, MEDIUM or FULL).

Request Body

{
  "query": "getUserMeta"
}

Success Response

{
  "success": true,
  "ranks": ["LTC", "CAPT", "FO", "JFO"],
  "vaPartners": [
    {
      "id": 1,
      "name": "VPT Airlines",
      "website": "https://...",
      "contactEmail": "contact@vptairlines.com",
      "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
    }
  ],
  "vaMemberships": [
    {
      "id": 1,
      "name": "VPT Airlines",
      "website": "https://...",
      "contactEmail": "contact@vptairlines.com",
      "isActive": true,
      "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
    }
  ],
  "activeVa": {
    "id": 1,
    "name": "VPT Airlines",
    "website": "https://...",
    "contactEmail": "contact@vptairlines.com",
    "logo": "https://api.virtualperformancetool.com/va/VPT_Airlines.png"
  }
}

Update Profile

Updates one or more profile fields for the authenticated user. Send only the fields you want to change — missing fields are left unchanged.

POST /api?query=updateProfile

Authentication

JWT required. Any user_info dev key level.

Editable fields

Field Type Notes
firstname string (max 50)
lastname string (max 50)
rank enum One of LTC, CAPT, FO, JFO
crewcode string (max 20)
discordID string (max 50)
pin string (4–6 digits) For settings sync
streamingActive boolean (0/1)
streamingAlias string (max 50)
activeVaId int|null Must be a VA the user is a member of (see vaMemberships). null clears the active VA.

Request Body

{
  "query": "updateProfile",
  "data": {
    "firstname": "John",
    "rank": "CAPT",
    "activeVaId": 1
  }
}

Success Response

{
  "success": true,
  "updated": ["firstname", "rank", "activeVaId"],
  "user": { /* full user object, see Refresh User Data */ }
}
About virtual_airline

The legacy virtual_airline ENUM column is now derived from activeVaId at API response time. New clients should write and read activeVaId exclusively. virtual_airline remains in responses for backwards compatibility and is set to null when the active VA is not part of the legacy ENUM list.

Profile / Alias Pictures

The user has two image slots: a profile picture (resized to a 512×512 max bounding box) and a streaming alias picture (resized to 1024×1024 max). Both are stored on the server and returned as absolute URLs in the user object as profilePicture and aliasPicture.

Constraints

  • Allowed MIME types: image/jpeg, image/png, image/webp
  • Maximum upload size: 5 MB
  • Server transcodes the image to JPEG (quality 88) and applies the resize

Upload

POST /api?query=uploadProfilePicture
POST /api?query=uploadAliasPicture

Send as multipart/form-data with a single file field named image. JWT in the standard Authorization header.

Delete

POST /api?query=deleteProfilePicture
POST /api?query=deleteAliasPicture

Success Response (upload)

{
  "success": true,
  "kind": "profile",
  "url": "https://api.virtualperformancetool.com/Library/userImages/42/profile-1730000000.jpg",
  "user": { /* full user object */ }
}

Request Email Change

Two-step flow to change the account email. The confirmation link is sent to the old email address, so an attacker who steals an active session cannot lock the user out of their account. On confirmation, all sessions on every device are invalidated and the user must log in again with the new email.

POST /api?query=requestEmailChange

Authentication

JWT required. The user_info dev key permission must be set to FULL — this is a sensitive operation.

Request Body

{
  "query": "requestEmailChange",
  "data": { "newEmail": "new@example.com" }
}

Success Response

{
  "success": true,
  "message": "A confirmation email has been sent to your current email address."
}

Confirmation Link

The email contains a one-time link to /api?query=confirmEmailChange&token=... — this is a public endpoint. Tokens are valid for 1 hour and can only be used once. Confirmation displays an HTML success page.

Errors

  • INSUFFICIENT_PERMISSIONS — dev key is not FULL user_info
  • INVALID_EMAIL — new email is missing or malformed
  • EMAIL_UNCHANGED — new email matches current
  • EMAIL_ALREADY_EXISTS — another account already uses that email

Request Account Deletion

Two-step flow that permanently deletes the account. The user receives a confirmation email; clicking the link runs the actual deletion.

POST /api?query=requestAccountDeletion

Authentication

JWT required. user_info dev key permission must be FULL. The demo account cannot be deleted.

What gets deleted

  • Cancelled: PayPal and 2Checkout subscriptions (no refund for unused time)
  • Deleted: users row, user_preferences, va_users memberships, profile/alias pictures (files + columns), user-owned library files (DB row + file on disk), user-owned library folders, library sync state, all subscription rows, any pending email-change or deletion tokens
  • Preserved: anonymized usage statistics (opt_statistics) and audit logs (logs_*)
Apple Pay subscriptions

Apple Pay subscriptions must be cancelled from the iOS device first (Settings > Apple ID > Subscriptions). Otherwise both requestAccountDeletion and confirmAccountDeletion will refuse with APPLE_SUBSCRIPTION_ACTIVE. No refund is provided for unused subscription time on any platform.

Request Body

{ "query": "requestAccountDeletion" }

Success Response

{
  "success": true,
  "message": "A deletion confirmation email has been sent. The link expires in 1 hour."
}

Confirmation Link

The email contains a one-time link to /api?query=confirmAccountDeletion&token=... — public endpoint, 1-hour TTL, single use. The page also invites the user to send feedback to info@virtualperformancetool.com.

Error Codes

The following error codes may be returned by User Management endpoints:

Error Code HTTP Status Description
AUTH_MISSING_DEV_KEY 400 No developer key provided in the request
AUTH_INVALID_DEV_KEY 401 The provided developer key is not valid
INSUFFICIENT_PERMISSIONS 403 Developer key does not have FULL user_info access (registration only)
MISSING_FIELD 400 A required field is missing from the request
INVALID_EMAIL 400 The email address format is invalid
INVALID_RANK 400 Invalid rank value (must be CAPT, FO, or LTC)
WEAK_PASSWORD 400 Password does not meet minimum length requirement (6 characters)
EMAIL_ALREADY_EXISTS 409 An account with the provided email already exists
INVALID_PREFERENCE 400 Invalid value for a userPreferences field (defaultUnit or multiplayerNetwork)
REGISTRATION_FAILED 500 Internal error during registration
INVALID_TOKEN 400 The password reset token is malformed or invalid
TOKEN_EXPIRED 400 The password reset link has expired (older than 1 hour)