Authentication

Learn how to authenticate with the VPT API using JWT tokens for secure, seamless access to all endpoints.

Overview

The VPT API uses JWT (JSON Web Tokens) for authentication. JWTs provide a secure, stateless way to authenticate requests without storing session data on the server.

30-Day Lifetime
Tokens are valid for 30 days with automatic sliding expiration.
Auto Refresh
Tokens refresh automatically when close to expiration.
Developer Keys
Dev keys are validated at login and embedded in tokens.

Authentication Flow

1
Login Request
Send your API key, developer key, and password to the login endpoint.
2
Receive JWT Token
Upon successful authentication, you receive a JWT token valid for 30 days.
3
Make API Requests
Include the token in the Authorization header for all subsequent requests.
4
Handle Refreshed Tokens
Watch for refreshed tokens in responses and store them for future use.

JWT Authentication

JWT tokens are self-contained and include all necessary information to validate requests. They are signed using the HS256 algorithm to ensure integrity.

Developer Key Access Levels

The developer key determines the level of user information returned during authentication. There are three access levels:

Access Level User Fields Returned Use Case
SIMPLE email, firstname, lastname, crewcode, rank, streamingAlias, streamingActive, virtualAirline, tokenVersion
userPreferences: defaultUnit, defaultAircraftOnStartup, takeoff settings only
Default level for basic user identification and minimal data
MEDIUM All SIMPLE fields plus: simbriefId, dateJoined, lastConnected, subscription
userPreferences: All settings including settingsLocked, displayIntegratedTutorial, weather, performance, export, simbrief, efb
Third-party integrations requiring detailed user profile and subscription data
FULL Complete user data including all database fields, the full subscription object (with embedded preview block), and full userPreferences with advanced settings Reserved for Virtual Performance Tool's official applications only
Requesting Access Levels

The default developer key (VirtualPerformanceTool) provides SIMPLE access level. When applying for a custom developer key, you can request MEDIUM or FULL access based on your application's needs. Contact VPT support with details about your integration to discuss access level requirements.

Login

To obtain a JWT token, send a POST request to the API with your credentials:

POST /api

Request Parameters

The request body uses a structured format with query, devKey, and data at the root level:

Parameter Type Required Description
query string Required Must be "login"
devKey string Required Your application's developer key
data.email string Required Your account email address
data.password string Required Your account password
cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "login",
    "devKey": "your_dev_key",
    "data": {
      "email": "your_email@example.com",
      "password": "your_password"
    }
  }'
JavaScript
const response = await fetch('https://api.virtualperformancetool.com/api', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        query: 'login',
        devKey: 'your_dev_key',
        data: {
            email: 'your_email@example.com',
            password: 'your_password'
        }
    })
});

const data = await response.json();

if (data.success) {
    // Store the token for future requests
    const token = data.token;
    localStorage.setItem('vpt_token', token);
}
PHP
<?php
$ch = curl_init('https://api.virtualperformancetool.com/api');

$data = [
    'query' => 'login',
    'devKey' => 'your_dev_key',
    'data' => [
        'email' => 'your_email@example.com',
        'password' => 'your_password'
    ]
];

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json'
    ]
]);

$response = curl_exec($ch);
$result = json_decode($response, true);

if ($result['success']) {
    $token = $result['token'];
    // Store token for future use
    $_SESSION['vpt_token'] = $token;
}
Python
import requests

response = requests.post(
    'https://api.virtualperformancetool.com/api',
    json={
        'query': 'login',
        'devKey': 'your_dev_key',
        'data': {
            'email': 'your_email@example.com',
            'password': 'your_password'
        }
    }
)

data = response.json()

if data.get('success'):
    token = data['token']
    # Store token for future use

Successful Response

JSON
{
  "success": true,
  "apiVersion": "1.3.6",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires": "2026-02-23T18:59:05+01:00",
  "expiresIn": 2592000,
  "user": {
    "email": "john.doe@example.com",
    "firstname": "John",
    "lastname": "Doe",
    "crewcode": "JD001",
    "rank": "CAPT",
    "streamingAlias": "LivePro",
    "streamingActive": 0,
    "virtualAirline": "VPT Airlines",
    "tokenVersion": 1,
    "subscription": {
      "title": "VPT Pro Annual",
      "level": 3,
      "status": "ACTIVE",
      "type": "pro",
      "expiry": "2026-12-31T23:59:59+01:00",
      "daysRemaining": 250,
      "isActive": true,
      "isExpired": false,
      "isTrial": false,
      "isPaused": false,
      "pauseUntil": null,
      "isAutoRenew": true,
      "paymentMethod": "paypal",
      "capabilities": {
        "isNotam": true,
        "isNnc": true,
        "isMel": true,
        "isCdl": true,
        "isCommercial": false,
        "isFleetDatabase": true,
        "maxCalculation": 1000
      },
      "preview": {
        "active": false,
        "reason": null,
        "allowedAirports": [],
        "message": null
      }
    },
    "userPreferences": {
      "defaultUnit": "METRIC",
      "defaultAircraftOnStartup": {
        "file": "320-212-CFM56-5A3.json",
        "userRegistration": null,
        "vaFleet": null
      },
      "takeoff": {
        "customizeStandardEngineFailureProcedure": true,
        "customizedText": "Maintain [RWY_TRACK]° to 25 NM...",
        "displayEngineFailureInFull": true
      }
    }
  }
}

The subscription object

The user.subscription object reflects the current subscription state for the account. It is included at MEDIUM and FULL access levels only. There is one and only one subscription object per user; the airport restriction (when applicable) is carried by the embedded preview block.

When the user has no row in the subscriptions table, the API synthesizes a PREVIEW subscription from the preview product (currently VPT Pro Annual, with the word “Annual” rewritten to “Demo” in the title). The synthesized object is isActive = true with the embedded preview.active = true block, so the client can render the full Pro UI; the API enforces the airport restriction server-side via preview.allowedAirports.

Field Type Description
title string|null Product title from the products table. For the synthesized PREVIEW subscription the words “Annual”, “Yearly” or “Monthly” are replaced with “Demo” (e.g. VPT Pro Demo).
level int|null Numeric subscription tier from the product.
status string Semantic computed status: ACTIVE — current and not paused; ACTIVE_PAUSED — current but renewal is paused (user retains full access until expiry); PAUSED — renewal paused and expiry has passed (gap period, no access); EXPIRED — lapsed normally; CANCELLED — explicitly cancelled. The synthesized PREVIEW subscription always reports ACTIVE with preview.active = true.
type string Lightweight tag from the users row (e.g. free, pro).
expiry string|null ISO 8601 date when the current subscription period ends. null for the synthesized PREVIEW subscription.
daysRemaining int|null Whole days until expiry (negative if expired). null for the synthesized PREVIEW subscription.
isActive bool True when date_end is in the future (regardless of status — a cancelled subscription continues to run until its end date). Always true for the synthesized PREVIEW subscription.
isExpired bool True when date_end is in the past (regardless of status).
isTrial bool True for short Apple Pay introductory periods (≤ 14 days).
isPaused bool True when the subscription renewal is currently paused (status is ACTIVE_PAUSED or PAUSED).
pauseUntil string|null ISO 8601 date when billing is scheduled to resume. null when the subscription is not paused. Present in both ACTIVE_PAUSED (renewal delayed; access still active) and PAUSED (gap period; preview mode active) states.
isAutoRenew bool True when the subscription will renew automatically.
paymentMethod string|null paypal | 2checkout | applepay | commercial. null for the synthesized PREVIEW subscription.
capabilities.isNotam bool NOTAM data accessible.
capabilities.isNnc bool Non-Normal Checklists accessible.
capabilities.isMel bool Minimum Equipment List accessible.
capabilities.isCdl bool Configuration Deviation List accessible.
capabilities.isCommercial bool Commercial-use license granted.
capabilities.isFleetDatabase bool Full fleet database accessible.
capabilities.maxCalculation int|null Maximum calculations per period (null = unlimited).
preview object Embedded preview-mode descriptor. See below.

The embedded subscription.preview block

Every subscription object carries an embedded preview block. When preview.active is true the user is granted the full Pro UI but the API only accepts requests against the airports listed in preview.allowedAirports; requests against any other ICAO are rejected server-side.

preview.active is true when:

  • The user has no real subscription row (synthesized PREVIEW subscription from product id 3, VPT Pro Demo).
  • The user's subscription date_end is in the past.

For real, currently-active subscriptions (including commercial subscriptions manually granted with no payment gateway), preview.active is false, allowedAirports is empty and the user has unrestricted access.

Field Type Description
active bool True when the user is in preview mode.
reason string|null no_subscription (no row in the subscriptions table) | expired (row exists but date_end is in the past), or null when not active.
allowedAirports string[] ICAO codes the user can run calculations against while in preview mode. Empty when active is false.
message string|null Human-readable label suitable for a UI badge. null when active is false.
Recommended client behaviour

When subscription.preview.active is true: show a “PREVIEW” badge, and pre-validate user-entered ICAOs against preview.allowedAirports before issuing calculation requests. Any other ICAO will be rejected by the server with an authorization error.

MEDIUM Access Level includes additional fields:

  • simbriefId - SimBrief account ID
  • dateJoined - Account creation timestamp
  • lastConnected - Last login timestamp
  • Extended userPreferences including: settingsLocked, displayIntegratedTutorial, weather, performance, export, simbrief, efb groups
User Data Fields

The exact fields returned in the user object depend on your developer key's access level:

  • SIMPLE (Default): 9 basic fields + limited userPreferences (defaultUnit, defaultAircraftOnStartup, takeoff)
  • MEDIUM: All SIMPLE fields + id, simbriefId, dateJoined, lastConnected + full userPreferences (excluding admin settings)
  • FULL: Complete database record including admin and internal fields (VPT apps only)

See Developer Key Access Levels section above for detailed field lists.

Error Response

JSON
{
  "success": false,
  "error": {
    "message": "Invalid email or password.",
    "code": "AUTH_INVALID_CREDENTIALS",
    "status": 401
  }
}
200 Authentication successful, token returned
400 Missing required parameters (email, password, or devKey)
401 Invalid email or password

Using Tokens

Once you have a JWT token, include it in the Authorization header of all API requests using the Bearer scheme:

HTTP Header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Example: Protected Request

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "performance",
    "data": { [...] }
  }'
Token Storage

Store your JWT token securely. For web applications, consider using httpOnly cookies or secure local storage. Never expose tokens in URLs or client-side code.

Token Refresh (Sliding Expiration)

To provide a seamless experience, the API implements sliding expiration. When a token is used and has less than 7 days remaining before expiration, a new token is automatically issued.

How It Works

  1. Token is valid for 30 days from issuance
  2. When token has < 7 days remaining and a request is made:
  3. The API returns a new token in the response
  4. Both old and new tokens remain valid until expiration
  5. Your application should store and use the new token

Response with Refreshed Token

When your token is refreshed, the response includes a new token:

JSON
{
  "success": true,
  "data": {
    // ... your requested data
  },
  "token_refreshed": true,
  "new_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 2592000
}

Handling Refreshed Tokens

JavaScript
async function apiRequest(endpoint, data) {
    const token = localStorage.getItem('vpt_token');
    
    const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
    });
    
    const result = await response.json();
    
    // Check if token was refreshed
    if (result.token_refreshed && result.new_token) {
        localStorage.setItem('vpt_token', result.new_token);
        console.log('Token refreshed automatically');
    }
    
    return result;
}
Never Re-login

With sliding expiration, users who are regularly active will never need to re-authenticate. The token automatically extends as long as they use the API at least once every 30 days.

Token Revocation

In case of account compromise, all existing tokens can be invalidated by changing the account password. This is implemented using a token version mechanism.

How Token Revocation Works

  1. Each JWT contains a version number (v claim)
  2. The user's current token version is stored in the database
  3. On each request, the token version is verified against the database
  4. When password is changed, the token version is incremented
  5. All tokens with older versions become invalid
Password Change = Logout Everywhere

Changing your password will invalidate all existing tokens. All devices and applications will need to re-authenticate. You can also explicitly invalidate all tokens using the Logout All Devices endpoint.

Handling Revoked Tokens

When a token has been revoked, the API returns:

JSON (HTTP 401)
{
  "success": false,
  "error": "Token has been revoked",
  "error_code": "AUTH_TOKEN_REVOKED"
}

Your application should handle this by prompting the user to log in again:

JavaScript
async function apiRequest(endpoint, data) {
    const response = await fetch(endpoint, { /* ... */ });
    
    if (response.status === 401) {
        const error = await response.json();
        
        if (error.error_code === 'AUTH_TOKEN_REVOKED') {
            // Clear stored token
            localStorage.removeItem('vpt_token');
            
            // Redirect to login
            window.location.href = '/login?reason=token_revoked';
            return;
        }
    }
    
    return response.json();
}

Logout All Devices

Invalidate all existing JWT tokens across every device and session. This uses the same token version mechanism as password changes, but without requiring a password change. A new token is returned so the current device stays logged in.

POST /api

Request Parameters

Parameter Type Required Description
query string Required Must be "logoutAll"

Request Example

cURL
curl -X POST "https://api.virtualperformancetool.com/api" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "logoutAll"
  }'
JavaScript
const response = await fetch('https://api.virtualperformancetool.com/api', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        query: 'logoutAll'
    })
});

const data = await response.json();

if (data.success) {
    // Replace stored token with the new one
    localStorage.setItem('vpt_token', data.token);
    console.log('Logged out from all other devices');
}

Successful Response

JSON
{
  "success": true,
  "message": "Successfully logged out from all other devices.",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires": "2026-04-20T18:59:05+01:00",
  "expiresIn": 2592000
}

Response Fields

Field Type Description
token string A new JWT token for the current session. All previous tokens are now invalid.
expires string ISO 8601 expiration date of the new token.
expiresIn integer Token lifetime in seconds (2592000 = 30 days).
Important: Replace Your Token

After calling logoutAll, you must replace your stored token with the new one returned in the response. The old token is no longer valid.

200 All tokens invalidated, new token returned for current session
401 Invalid or expired JWT token
500 Failed to invalidate tokens (database error)

Next Steps

Now that you understand authentication, you're ready to:

Integration Flow
Performance API
Error Handling