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.
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.
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.
/api
Request Body
{
"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
|
|||
| 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
{
"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,
...
},
...
}
}
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 -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.
/api
Request Body
{
"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
{
"success": true,
"message": "If an account with this email exists, a password reset link has been sent."
}
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.
/api
Request Body
{
"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
{
"success": true,
"message": "Password has been reset successfully. You can now log in with your new password.",
"returnUrl": "vpt://password-reset-success"
}
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
Client calls requestPasswordReset with user's
email
Server sends reset email with a tokenized link (1 hour expiry)
Client calls resetPassword with the token and new
password
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.
The legacy query name refreshUserPreferences still
works and is an exact alias for refreshUserData.
/api
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
{
"query": "refreshUserData"
}
Example Request
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
{
"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": [ ... ]
}
}
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 tousers.emailonly export.exportURL
efb Group (API 1.3.0)
The new top-level efb object groups in-app EFB module
toggles:
-
enableEFB— master switch (defaulttrue) -
modules.briefingNavlog,modules.checklist,modules.library,modules.tools— per-module toggles (defaultstrue) -
toolsOptions— opaque JSON store synchronized across this user's clients.nullmeans "use the client defaults". When set, must be an array of objects{id, visible:bool, order:int}.
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 (defaulttrue) -
weather.notification— opt-in to the new weather notification stream (defaulttrue)
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. |
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).
/api
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)
{
"query": "saveUserSettings",
"data": {
"defaultUnit": "IMPERIAL"
}
}
Request Body — EFB module toggle (nested partial)
{
"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
{
"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
{
"success": true,
"fieldsUpdated": 1
}
Validation Errors
{
"error": "Validation failed",
"details": [
"defaultUnit: must be one of: METRIC, METRIC_M, IMPERIAL, HYBRID1, HYBRID2, HYBRID3",
"efb.toolsOptions: toolsOptions[2].order is required"
]
}
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.
/api
This endpoint requires a valid JWT token in the
Authorization header. No devKey is
needed when using JWT authentication.
Request Body
{
"query": "userStatistics"
}
Example Request
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
{
"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.
|
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.
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.
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 */ }
}
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
Send as multipart/form-data with a single file field
named image. JWT in the standard
Authorization header.
Delete
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.
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.
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:
usersrow,user_preferences,va_usersmemberships, 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
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) |