Your API base URLs now include a tier prefix - /public, /partner, or /internal that must be added to every API call.
URL Change RequiredReplace 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 RequiredThis 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 |
API Version: Now in the URL Path
The ResourceVersion request header is no longer supported. Specify the API version directly in the URL path instead.
Previously you could select the API resource version by passing a ResourceVersion header on each request. This header is now deprecated and will be ignored. The version must be included as a fixed segment in the URL path.
| Before Header-based versioning | New URL path versioning |
|---|---|
/public-flights/flightsResourceVersion: v4 |
/public-flights/v4/flights |
The current version is v4. Always include the version segment when constructing your request URLs.
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.
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
Create an application on the portal for your API tier. Your credentials are shown once - store the Client Secret immediately in a secrets manager.
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.
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
- Go to the Developer Portal for your API tier (see sidebar for links)
- Log in or sign up (public portal requires registration)
- Navigate to Applications and create a new application
- Copy your Client ID and Client Secret - the Secret is shown only once
- Subscribe to the APIs you need and await approval if required
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:
| Environment | Token Endpoint |
|---|---|
| PRD | https://api.auth.schiphol.nl/oauth/token |
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be client_credentials |
client_id | Yes | Your application's Client ID |
client_secret | Yes | Your application's Client Secret |
audience | Yes | API audience URL, e.g. https://api.schiphol.nl/public |
curl -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=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "audience=https://api.schiphol.nl/public"
The audience identifies which API you are requesting a token for. It must exactly match the tier and environment of the API you intend to call, otherwise the token is rejected with a 401 or 403. For the public tier use https://api.schiphol.nl/public. Each token is scoped to a single audience, so request a separate token per tier.
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, timedeltaclass 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("/public-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<String> getFlights() {
return api.get()
.uri("/public-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('/public-flights/v4/flights')
.then(r => console.log(r.data))
.catch(console.error);
3. Best Practices
Token Management
- 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
- Request a new token for every API call
- Wait for a 401 error before refreshing
- Share token state without synchronisation
- Ignore the
expires_inresponse field
Secret Management
- Store secrets in environment variables or a vault
- Use Azure Key Vault, HashiCorp Vault, or similar
- Rotate secrets regularly
- Use separate credentials per environment
- 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
| Cause | Solution |
|---|---|
| Token expired | Check your refresh logic - read expires_in and refresh 60 s before expiry. |
| Invalid credentials | Verify Client ID and Secret in the Developer Portal or create a new application. |
| Missing Authorization header | Ensure every request includes Authorization: Bearer <token>. |
| Wrong audience | The audience in your token request must match the API tier URL you are calling. |
HTTP 403 Forbidden
| Cause | Solution |
|---|---|
| API not subscribed | Subscribe to the API in the Developer Portal. Only approved subscriptions grant access. |
| Subscription pending approval | Some APIs require manual approval. Check your application status in the portal. |
| Insufficient scope | Your 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.
| Endpoint | Limit |
|---|---|
| Token endpoint (Auth0) | See Auth0 rate limit policy |
| API endpoints | Varies by API - see each API's documentation |
Token Request Errors
| Error | Cause | Solution |
|---|---|---|
invalid_client | Wrong Client ID or Secret | Double-check credentials in the Developer Portal |
invalid_grant | Incorrect grant type | Use grant_type=client_credentials |
rate_limit_exceeded | Too many token requests | Cache and reuse tokens; refresh only when near expiry |
unauthorized_client | Client not authorised for this grant type | Verify 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.