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 Required2
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 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 |
Developer Portal URLs by environment
| Portal Type | Previous | PRD | ACC |
|---|---|---|---|
| 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
- 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
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:
| Environment | Token Endpoint |
|---|---|
| ACC | https://api-acc.auth.schiphol.nl/oauth/token |
| 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-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, 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("/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<String> 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_inresponse 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
| 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.