Overview
The SkyTeam API provides a RESTful interface for managing flights, users, airline data, and integration with external systems. This guide covers authentication, common endpoints, and integration patterns.
API Architecture
The API is built with Express.js and follows REST principles:
Base URL: http://localhost:4000 (development) / https://api.skyteam.dev (production)
Protocol: HTTPS (production), HTTP (development)
Format: JSON request/response
Authentication: API key via x-api-key header
Authentication
API Key Authentication
All authenticated endpoints require an API key passed in the x-api-key header:
apps/api/src/middleware/auth.ts
export async function airlineAuth (
req : Request ,
res : Response ,
next : NextFunction ,
) {
try {
const token = req . header ( "x-api-key" );
if ( ! token ) {
return res . status ( 401 ). json ({ error: "Missing x-api-key header" });
}
const airline = await fetchAirlineByToken ( token );
if ( ! airline ) {
return res . status ( 401 ). json ({ error: "Invalid API key" });
}
// Attach to locals for downstream handlers
res . locals . airline = airline ;
res . locals . safeAirline = safeAirline ( airline );
return next ();
} catch ( err ) {
return next ( err );
}
}
Making Authenticated Requests
JavaScript/Node.js
Python
cURL
ROBLOX/Luau
const response = await fetch ( 'http://localhost:4000/airline' , {
method: 'GET' ,
headers: {
'x-api-key' : 'your-api-key-here' ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
console . log ( data );
Security Considerations
Never expose your API key in client-side code or commit it to version control. Use environment variables or secure configuration management.
// Bad - Hardcoded in source
const API_KEY = "abc123token" ;
// Good - From environment
const API_KEY = process . env . SKYTEAM_API_KEY ;
Core Endpoints
Health Check
Public endpoint - No authentication required
Returns server health status without database dependency:
Get Current Airline
Returns authenticated airline information (token excluded):
apps/api/src/routes/airline.ts
router . get ( "/airline" , async ( _req , res , next ) => {
try {
const safeAirline = res . locals . safeAirline ?? res . locals . airline ;
res . json ( safeAirline );
} catch ( err ) {
next ( err );
}
});
Response:
{
"airlineId" : "abc-123" ,
"name" : "SkyTeam Airways" ,
"code" : "SKY" ,
"createdAt" : "2024-01-01T00:00:00.000Z"
}
Get Airline Products
GET /airline/fetchProductsData
Returns loyalty program products and miles offerings:
apps/api/src/routes/airline.ts
router . get ( "/airline/fetchProductsData" , async ( _req , res , next ) => {
try {
const airline = res . locals . airline as { airlineId : string };
const products = await fetchMilesProducts ( airline . airlineId );
res . json ( products );
} catch ( err ) {
next ( err );
}
});
Flight Management
Get Upcoming Flights
GET /flight/fetchUpcomingFlights
Returns upcoming flights with brand information:
apps/api/src/routes/flight.ts
router . get ( "/flight/fetchUpcomingFlights" , async ( _req , res , next ) => {
try {
const airline = res . locals . airline as { airlineId : string };
const [ flights , brands ] = await Promise . all ([
fetchComingFlights ( airline . airlineId ),
fetchAirlineBrands ( airline . airlineId ),
]);
const brandsById = new Map ( brands . map (( b ) => [ b . brandId , b ]));
const withBrand = flights . map (( f ) => ({
... f ,
brand: brandsById . get ( f . brandId ) || null ,
}));
res . json ( withBrand );
} catch ( err ) {
next ( err );
}
});
Response:
[
{
"flightId" : "flight-123" ,
"flightNumber" : "SKY101" ,
"departure" : "JFK" ,
"arrival" : "LAX" ,
"scheduledTime" : "2026-03-04T10:00:00.000Z" ,
"brand" : {
"brandId" : "brand-1" ,
"name" : "SkyTeam Connect" ,
"description" : "Regional service"
}
}
]
Start Flight
POST /flight/:id/serverStart
Marks a flight as started and records the startedAt timestamp:
apps/api/src/routes/flight.ts
router . post ( "/flight/:id/serverStart" , async ( req , res , next ) => {
try {
const { id } = req . params ;
const updated = await startFlight ( id );
if ( ! updated )
return res . status ( 404 ). json ({ error: "Flight not found" });
res . json ( updated );
} catch ( err ) {
next ( err );
}
});
Example Request:
fetch ( 'http://localhost:4000/flight/flight-123/serverStart' , {
method: 'POST' ,
headers: {
'x-api-key' : 'your-api-key' ,
'Content-Type' : 'application/json'
}
});
Flight Heartbeat
Sends a heartbeat to indicate the flight server is still active:
apps/api/src/routes/flight.ts
router . post ( "/flight/:id/tick" , async ( req , res , next ) => {
try {
const { id } = req . params ;
// In future: persist lastPingAt and auto-end if stale
res . json ({ ok: true , flightId: id , message: "tick received" });
} catch ( err ) {
next ( err );
}
});
The heartbeat endpoint currently acknowledges receipt but doesn’t persist timestamps. Future versions will track lastPingAt and automatically end stale flights.
End Flight
POST /flight/:id/serverEnd
Marks a flight as completed:
apps/api/src/routes/flight.ts
router . post ( "/flight/:id/serverEnd" , async ( req , res , next ) => {
try {
const { id } = req . params ;
const updated = await endFlight ( id );
if ( ! updated )
return res . status ( 404 ). json ({ error: "Flight not found" });
res . json ( updated );
} catch ( err ) {
next ( err );
}
});
User Management
Manage user accounts and authentication (see apps/api/src/routes/users.ts for implementation details).
Error Handling
The API uses standard HTTP status codes and returns JSON error responses:
// Not found
app . use (( req , res ) => {
res . status ( 404 ). json ({ error: "Not Found" , path: req . path });
});
// Error handler
app . use (
( err : any , _req : express . Request , res : express . Response , _next : express . NextFunction ) => {
console . error ( "Unhandled error:" , err );
res . status ( 500 ). json ({ error: "Internal Server Error" });
},
);
Common Status Codes
Code Meaning Example 200 Success Request completed successfully 401 Unauthorized Missing or invalid API key 404 Not Found Resource doesn’t exist 500 Internal Server Error Unexpected server error
{
"error" : "Missing x-api-key header"
}
Or with additional context:
{
"error" : "Not Found" ,
"path" : "/invalid-endpoint"
}
Integration Patterns
Pattern 1: Flight Lifecycle Management
Track a flight from scheduling to completion:
// 1. Fetch upcoming flights
const flights = await fetch ( 'http://localhost:4000/flight/fetchUpcomingFlights' , {
headers: { 'x-api-key' : API_KEY }
}). then ( r => r . json ());
// 2. Start the first flight
const flight = flights [ 0 ];
await fetch ( `http://localhost:4000/flight/ ${ flight . flightId } /serverStart` , {
method: 'POST' ,
headers: { 'x-api-key' : API_KEY }
});
// 3. Send periodic heartbeats
const heartbeat = setInterval ( async () => {
await fetch ( `http://localhost:4000/flight/ ${ flight . flightId } /tick` , {
method: 'POST' ,
headers: { 'x-api-key' : API_KEY }
});
}, 30000 ); // Every 30 seconds
// 4. End the flight
clearInterval ( heartbeat );
await fetch ( `http://localhost:4000/flight/ ${ flight . flightId } /serverEnd` , {
method: 'POST' ,
headers: { 'x-api-key' : API_KEY }
});
Pattern 2: ROBLOX Game Integration
Integrate with a ROBLOX game server:
local HttpService = game : GetService ( "HttpService" )
local API_KEY = "your-api-key"
local BASE_URL = "http://localhost:4000"
-- Fetch upcoming flights on server start
local function fetchFlights ()
local response = HttpService : RequestAsync ({
Url = BASE_URL .. "/flight/fetchUpcomingFlights" ,
Method = "GET" ,
Headers = {
[ "x-api-key" ] = API_KEY ,
[ "Content-Type" ] = "application/json"
}
})
if response . Success then
return HttpService : JSONDecode ( response . Body )
else
warn ( "Failed to fetch flights:" , response . StatusCode )
return nil
end
end
-- Start a flight
local function startFlight ( flightId )
HttpService : RequestAsync ({
Url = BASE_URL .. "/flight/" .. flightId .. "/serverStart" ,
Method = "POST" ,
Headers = {
[ "x-api-key" ] = API_KEY ,
[ "Content-Type" ] = "application/json"
}
})
end
-- Heartbeat loop
local function startHeartbeat ( flightId )
spawn ( function ()
while true do
wait ( 30 )
HttpService : RequestAsync ({
Url = BASE_URL .. "/flight/" .. flightId .. "/tick" ,
Method = "POST" ,
Headers = {
[ "x-api-key" ] = API_KEY
}
})
end
end )
end
Pattern 3: Dashboard Integration
Build a monitoring dashboard:
import { useEffect , useState } from 'react' ;
function FlightDashboard () {
const [ flights , setFlights ] = useState ([]);
const [ airline , setAirline ] = useState ( null );
useEffect (() => {
const fetchData = async () => {
const headers = {
'x-api-key' : process . env . REACT_APP_API_KEY ,
'Content-Type' : 'application/json'
};
// Fetch airline info and flights in parallel
const [ airlineRes , flightsRes ] = await Promise . all ([
fetch ( 'http://localhost:4000/airline' , { headers }),
fetch ( 'http://localhost:4000/flight/fetchUpcomingFlights' , { headers })
]);
setAirline ( await airlineRes . json ());
setFlights ( await flightsRes . json ());
};
fetchData ();
const interval = setInterval ( fetchData , 60000 ); // Refresh every minute
return () => clearInterval ( interval );
}, []);
return (
< div >
< h1 > { airline ?. name } Dashboard </ h1 >
< ul >
{ flights . map ( flight => (
< li key = { flight . flightId } >
{ flight . flightNumber } : { flight . departure } → { flight . arrival }
</ li >
)) }
</ ul >
</ div >
);
}
Rate Limiting
Currently, the API does not enforce rate limiting. Implement client-side throttling for high-frequency operations:
// Simple rate limiter
class RateLimiter {
constructor ( maxRequests , intervalMs ) {
this . maxRequests = maxRequests ;
this . intervalMs = intervalMs ;
this . queue = [];
}
async throttle ( fn ) {
const now = Date . now ();
this . queue = this . queue . filter ( time => now - time < this . intervalMs );
if ( this . queue . length >= this . maxRequests ) {
const oldestRequest = this . queue [ 0 ];
const waitTime = this . intervalMs - ( now - oldestRequest );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
}
this . queue . push ( Date . now ());
return fn ();
}
}
// Usage: Max 10 requests per minute
const limiter = new RateLimiter ( 10 , 60000 );
await limiter . throttle (() =>
fetch ( 'http://localhost:4000/flight/fetchUpcomingFlights' , {
headers: { 'x-api-key' : API_KEY }
})
);
Security Features
Helmet.js Protection
The API uses Helmet.js for security headers:
This enables:
Content Security Policy
X-Frame-Options
X-Content-Type-Options
Strict-Transport-Security (HTTPS)
CORS Configuration
CORS is enabled for all origins in development. Configure for production:
app . use ( cors ({
origin: [ 'https://skyteam.dev' , 'https://admin.skyteam.dev' ],
credentials: true
}));
Testing
Using the Health Endpoint
Verify API connectivity without authentication:
curl http://localhost:4000/health
Expected response:
Testing Authentication
Test with an invalid key to verify error handling:
curl -X GET http://localhost:4000/airline \
-H "x-api-key: invalid-key"
Expected response:
{ "error" : "Invalid API key" }
Troubleshooting
Cause: Missing or invalid API keySolutions:
Verify x-api-key header is included
Check that your API key is active and correct
Ensure the key hasn’t been rotated or revoked
Contact alliance administrators for a new key
Cause: Endpoint doesn’t exist or resource not foundSolutions:
Verify the endpoint URL is correct
Check API documentation for correct paths
Ensure flight/user IDs are valid
Confirm you’re using the correct HTTP method
Cause: Browser blocking cross-origin requestsSolutions:
Use a server-side proxy for browser requests
Configure CORS on the API server
Use the API from server-side code instead
In development, use browser CORS extensions (not recommended for production)
Cause: API server not running or wrong URLSolutions:
Verify the API server is running
Check the base URL matches your environment
Ensure port 4000 isn’t blocked by firewall
Try the /health endpoint to verify connectivity
Next Steps
ROBLOX Setup Integrate the SkyTeam module in your game
Discord Bot Set up Discord notifications and commands