Migration Guide
What's Changing
Two changes required to update your integration
1
Update Your API Endpoint URLs

Your API base URLs now include a tier prefix - /public, /partner, or /internal that must be added to every API call.

URL Change Required
2
Switch to OAuth 2.0 Authentication

Replace App_Id / App_Key headers with OAuth 2.0 Client Credentials. Obtain a short-lived JWT token from Auth0 and include it as a Bearer token on every API call.

Auth Change Required

This guide is for teams who need to update their integration with the Schiphol API platform. Two changes are required: update your endpoint URLs and switch to OAuth 2.0 authentication.

1. Endpoint Changes

Every API endpoint is now prefixed with its tier type: /public, /partner, or /internal. Update all your API call URLs to include this prefix.

API Type Before Previous Endpoint New Updated Endpoint
Public api.schiphol.nl/public-flights/flights api.schiphol.nl/public/public-flights/flights
Developer Portal URLs by environment +
Portal TypePreviousPRDACC
Public developer.schiphol.nl developer.schiphol.nl developer-acc.schiphol.nl

2. Authentication: OAuth 2.0 Client Credentials

The previous authentication method used static API key headers sent with every request. The Schiphol API platform uses the industry-standard OAuth 2.0 Client Credentials Flow, designed for machine-to-machine communication with short-lived, secure tokens.

What changes?

Instead of sending App_Id and App_Key on every call, you exchange your Client ID & Secret for a JWT token once, then include that token as a Bearer header on every API call. Tokens expire after 30 minutes and must be refreshed.

Authentication flow overview

1
Get Client ID & Secret from the Developer Portal

Create an application on the portal for your API tier. Your credentials are shown once - store the Client Secret immediately in a secrets manager.

2
Request a JWT token from Auth0

POST your Client ID, Secret, and API audience to the token endpoint. You receive a JWT valid for 30 minutes. Cache it and reuse it for all subsequent calls.

3
Include the token in every API call

Add the JWT to the Authorization: Bearer <token> header. Multiple requests share one token until expiry. Refresh proactively - 60 seconds before the token expires.

Step 1 - Get your credentials

  1. Go to the Developer Portal for your API tier (see sidebar for links)
  2. Log in or sign up (public portal requires registration)
  3. Navigate to Applications and create a new application
  4. Copy your Client ID and Client Secret - the Secret is shown only once
  5. Subscribe to the APIs you need and await approval if required
Security Notice

Your Client Secret is shown only once. Store it immediately in a secrets manager (e.g. Azure Key Vault). Never commit secrets to source control or expose them in client-side code.

Step 2 - Request a JWT token

Send a POST request to the Auth0 token endpoint for your target environment:

EnvironmentToken Endpoint
ACChttps://api-acc.auth.schiphol.nl/oauth/token
PRDhttps://api.auth.schiphol.nl/oauth/token
ParameterRequiredDescription
grant_typeYesMust be client_credentials
client_idYesYour application's Client ID
client_secretYesYour application's Client Secret
audienceYesAPI audience URL, e.g. https://api.schiphol.nl/public
curl -X POST "https://api-acc.auth.schiphol.nl/oauth/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=client_credentials" \
 -d "client_id=YOUR_CLIENT_ID" \
 -d "client_secret=YOUR_CLIENT_SECRET" \
 -d "audience=https://api-acc.schiphol.nl/public"

Expected response:

{
 "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
 "token_type": "Bearer",
 "expires_in": 1800
}

Step 3 - Call the API with your token

Add the access_token as a Bearer token in the Authorization header on every API call.

curl -X GET "https://api.schiphol.nl/public/public-flights/v4/flights" \
 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
 -H "Accept: application/json"

Code Examples

Choose your language below for a complete, production-ready implementation with automatic token caching and refresh.

cURL: Quick Testing +
# Step 1: Fetch and store the access token
TOKEN=$(curl -s -X POST https://api.auth.schiphol.nl/oauth/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=client_credentials" \
 -d "client_id=${SCHIPHOL_CLIENT_ID}" \
 -d "client_secret=${SCHIPHOL_CLIENT_SECRET}" \
 -d "audience=https://api.schiphol.nl/public" \
 | jq -r '.access_token')

Step 2: Call the API using the stored token

curl -X GET "https://api.schiphol.nl/public/public-flights/v4/flights"
-H "Authorization: Bearer ${TOKEN}"
-H "Accept: application/json"

Python: with automatic token refresh +
import os
import threading
import requests
from datetime import datetime, timedelta

class SchipholAPIClient: TOKEN_URL = "https://api.auth.schiphol.nl/oauth/token" BASE_URL = "https://api.schiphol.nl/public" AUDIENCE = "https://api.schiphol.nl/public"

def __init__(self, client_id: str, client_secret: str):
    self.client_id = client_id
    self.client_secret = client_secret
    self._token = None
    self._expires_at = None
    self._lock = threading.Lock()

def _get_token(self) -> str:
    with self._lock:
        buffer = timedelta(seconds=60)
        if not self._token or datetime.utcnow() >= self._expires_at - buffer:
            r = requests.post(self.TOKEN_URL, data={
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "audience": self.AUDIENCE,
            })
            r.raise_for_status()
            data = r.json()
            self._token = data["access_token"]
            self._expires_at = datetime.utcnow() + timedelta(seconds=data["expires_in"])
    return self._token

def get(self, path: str, **kwargs) -> requests.Response:
    headers = kwargs.pop("headers", {})
    headers["Authorization"] = f"Bearer {self._get_token()}"
    return requests.get(f"{self.BASE_URL}{path}", headers=headers, **kwargs)

Usage

client = SchipholAPIClient( client_id=os.environ"SCHIPHOL_CLIENT_ID", client_secret=os.environ"SCHIPHOL_CLIENT_SECRET", ) print(client.get("/operational-flights/v4/flights").json())

Java: Spring WebClient (OAuth2) +
# application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          schiphol:
            client-id: ${SCHIPHOL_CLIENT_ID}
            client-secret: ${SCHIPHOL_CLIENT_SECRET}
            authorization-grant-type: client_credentials
            provider: schiphol
        provider:
          schiphol:
            token-uri: https://api.auth.schiphol.nl/oauth/token
// SchipholApiConfig.java
@Configuration
public class SchipholApiConfig {
@Bean
public WebClient schipholApiClient(
        ReactiveClientRegistrationRepository registrations,
        ServerOAuth2AuthorizedClientRepository clients) {

    var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(registrations, clients);
    oauth.setDefaultClientRegistrationId("schiphol");

    return WebClient.builder()
            .baseUrl("https://api.schiphol.nl/public")
            .filter(oauth)
            .build();
}

}

// Usage in a service @Service public class FlightsService {

private final WebClient api;

public FlightsService(WebClient schipholApiClient) {
    this.api = schipholApiClient;
}

public Mono&lt;String&gt; getDepartures() {
    return api.get()
            .uri("/operational-flights/v4/flights")
            .retrieve()
            .bodyToMono(String.class);
}

}

Node.js: with automatic token refresh +
const axios = require('axios');

class SchipholAPIClient { constructor(clientId, clientSecret) { this.tokenUrl = 'https://api.auth.schiphol.nl/oauth/token'; this.baseUrl = 'https://api.schiphol.nl/public'; this.audience = 'https://api.schiphol.nl/public'; this.clientId = clientId; this.clientSecret = clientSecret; this.token = null; this.expiresAt = null; }

async getToken() { const BUFFER = 60000; // refresh 60 s before expiry if (!this.token || Date.now() >= this.expiresAt - BUFFER) { const { data } = await axios.post( this.tokenUrl, new URLSearchParams({ grant_type: 'client_credentials', client_id: this.clientId, client_secret: this.clientSecret, audience: this.audience, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } ); this.token = data.access_token; this.expiresAt = Date.now() + data.expires_in * 1000; } return this.token; }

async get(path, opts = {}) { const token = await this.getToken(); return axios.get(${this.baseUrl}${path}, { ...opts, headers: { ...opts.headers, Authorization: Bearer ${token} }, }); } }

const client = new SchipholAPIClient( process.env.SCHIPHOL_CLIENT_ID, process.env.SCHIPHOL_CLIENT_SECRET ); client.get('/operational-flights/v4/flights') .then(r => console.log(r.data)) .catch(console.error);

3. Best Practices

Token Management +
Do
  • Cache tokens and reuse until near expiry
  • Refresh proactively, 60 s before expiry
  • Use thread-safe token storage in concurrent apps
  • Implement exponential backoff on token failures
Don't
  • Request a new token for every API call
  • Wait for a 401 error before refreshing
  • Share token state without synchronisation
  • Ignore the expires_in response field
Secret Management +
Do
  • Store secrets in environment variables or a vault
  • Use Azure Key Vault, HashiCorp Vault, or similar
  • Rotate secrets regularly
  • Use separate credentials per environment
Don't
  • Hardcode secrets in source code
  • Commit secrets to version control
  • Share credentials across environments
  • Log or print secrets in error messages

4. Troubleshooting

HTTP 401  Unauthorized +
CauseSolution
Token expiredCheck your refresh logic - read expires_in and refresh 60 s before expiry.
Invalid credentialsVerify Client ID and Secret in the Developer Portal or create a new application.
Missing Authorization headerEnsure every request includes Authorization: Bearer <token>.
Wrong audienceThe audience in your token request must match the API tier URL you are calling.
HTTP 403  Forbidden +
CauseSolution
API not subscribedSubscribe to the API in the Developer Portal. Only approved subscriptions grant access.
Subscription pending approvalSome APIs require manual approval. Check your application status in the portal.
Insufficient scopeYour application may need additional permissions - contact the API team.
HTTP 429  Too Many Requests +

You have exceeded a rate limit. Implement exponential backoff and ensure you are caching tokens rather than requesting a new one on every call.

EndpointLimit
Token endpoint (Auth0)See Auth0 rate limit policy
API endpointsVaries by API - see each API's documentation
Token Request Errors +
ErrorCauseSolution
invalid_clientWrong Client ID or SecretDouble-check credentials in the Developer Portal
invalid_grantIncorrect grant typeUse grant_type=client_credentials
rate_limit_exceededToo many token requestsCache and reuse tokens; refresh only when near expiry
unauthorized_clientClient not authorised for this grant typeVerify your application settings in the portal

Need Help?

Check the API catalogue for endpoint details, or contact the Schiphol API team for support. You can also visit the Getting Started Guide for a step-by-step introduction to the platform.