API Best Practices - Data Flow Diagram
Best Practices
Six areas every API integration should get right
1
Rate Limiting

Understand per-tier limits, monitor quota headers, and implement exponential backoff with jitter.

Performance
2
Token Management

Cache tokens, refresh proactively 60 s before expiry, and use thread-safe storage in concurrent apps.

Reliability
3
Caching

Match cache TTL to data freshness - from 24 h for static data to 15 s for real-time status.

Performance
4
Error Handling

Handle every HTTP status code and design with circuit breakers and graceful fallbacks.

Reliability
5
Security

Store secrets in a vault, use per-environment credentials, and rotate regularly.

Security
6
Testing

ACC access is not automatic - even Partner accounts must request it. Contact the API team to get access.

Quality

Building robust integrations with Schiphol APIs requires following patterns that ensure reliability, performance, and a smooth experience for your users. This guide covers the essential practices every developer should apply - whether you are building a new integration from scratch or hardening an existing one.

1. Rate Limiting

All Schiphol APIs enforce rate limits per application to ensure fair usage and platform stability. Exceeding a limit returns a 429 Too Many Requests response with a Retry-After header indicating how long to wait.

TierRate LimitNotes
Public100 requests / minuteSuitable for display and informational applications
Partner1,000 requests / minuteFor commercial and integration partners
Internal10,000 requests / minuteSchiphol internal systems only

2. Token Management

OAuth 2.0 access tokens are valid for 30 minutes. Mismanaging tokens is the most common cause of 401 Unauthorized errors in production.

1
Request a token once - then cache and reuse it

Never request a new token for every API call. A single token is valid for 30 minutes and should serve all requests within that window. Requesting a new token per call wastes Auth0 quota and adds 100-200 ms latency to every request.

2
Refresh proactively - 60 seconds before expiry

Read expires_in from the token response (usually 1800 s = 30 min). Schedule a background refresh 60 s before the token expires. This ensures you always have a valid token ready and avoids in-flight requests receiving a 401.

3
Use thread-safe storage in concurrent applications

In multi-threaded or async apps, protect token state with a mutex or lock. Without synchronisation, multiple threads may all detect token expiry simultaneously and each request a new token - this is the "token stampede" problem and will hit Auth0 rate limits.

4
Store Client Secret in a secrets manager

Never hardcode Client ID or Client Secret in source code or config files. Use Azure Key Vault, HashiCorp Vault, or environment variables. Rotate secrets at least every 90 days and immediately if a secret is exposed.

Token stampede warning

In high-concurrency systems, if the token expires and 50+ threads all detect it simultaneously, they may all try to refresh at once. Use a single background token-refresh worker and share the token reference with all threads via a read-write lock.

3. Caching Strategies

Caching is the single most effective way to reduce API call volume, lower latency, and stay within rate limits. Different data types have different freshness requirements - match your TTL to the data, not to a single global setting.

Data TypeRecommended TTLNotes
Static reference data - airlines, destinations, aircraft types24 hoursChanges very rarely; safe to cache aggressively
Flight schedules5-15 minutesChanges a few times per day; short TTL is sufficient
Flight status - gate, delay, boarding30-60 secondsTime-sensitive; keep cache short
Real-time arrivals / departures15-30 secondsEven a 15 s cache dramatically cuts repeated identical calls
User-specific / subscription dataDo not cacheNever share personalised data across callers
Application-level vs. per-user caches+

Flight schedule and status data is the same for all users - cache it at the application level and serve all users from a single cache entry. Never cache data that contains user-specific subscription or authorisation information in a shared cache.

Stale-while-revalidate pattern+

For non-critical data, serve the stale cached version immediately while refreshing in the background. This eliminates cache-miss latency spikes at TTL boundaries. Suitable for flight schedules and reference data - do not use for real-time status data where freshness is critical.

4. Error Handling

Robust error handling ensures your application degrades gracefully rather than failing hard when the API returns an unexpected response. Design every integration to handle failure as a first-class concern.

StatusMeaningRecommended Action
200-299SuccessProcess the response normally
304Not ModifiedUse your cached copy - nothing has changed
400Bad RequestFix the request (invalid parameter, missing field) - do not retry as-is
401UnauthorizedRefresh the access token and retry the request once
403ForbiddenCheck subscription status - approval may be pending; contact the API team
404Not FoundValidate the flight number, ID, or resource path
429Too Many RequestsBack off exponentially; always honour the Retry-After header
500Server ErrorRetry with exponential backoff - do not try to fix in the client
503Service UnavailableThe service is temporarily down; retry after the Retry-After period
Design for failure from the start

Treat every external API call as potentially failing. Use timeouts (5 s connect / 10 s read), circuit breakers to stop calling a failing API, and fallback values so a temporary API issue does not cascade into a full application outage.

Which errors are safe to retry?+

Safe to retry (with backoff): 429, 500, 502, 503, 504, and network timeouts.

Do not retry: 400 (fix your request first), 401 (refresh token then retry once), 403 (subscription issue - retrying won't help), 404 (resource does not exist).

5. API Versioning

Always pin your integration to a specific API version. Calling an unversioned endpoint may result in silent breaking changes when we release a new major version.

  • Specify the version in the URL path: /public-flights/v4/flights
  • Subscribe to the News & Updates page for version announcements and deprecation notices
  • If you have ACC access, test against the new version there before migrating production
  • Allow at least 2 weeks of parallel testing before switching your production integration
Version support policy

We support each major API version for at least 18 months after the next version is released. Deprecation notices are published at least 6 months before end-of-life. You will never wake up to a surprise breaking change.

6. Security

Credential exposure is the most common security incident in API integrations. Follow these rules without exception.

Do
  • Store Client ID & Secret in a secrets manager (Azure Key Vault, HashiCorp Vault)
  • Use separate credentials per environment (dev, test, production)
  • Rotate secrets at least every 90 days
  • Revoke and reissue immediately if a secret is exposed
  • Validate and sanitise all API response data before using it
Don't
  • Hardcode secrets in source code or config files
  • Commit secrets to version control - even private repos
  • Expose the Client Secret in client-side JavaScript or mobile apps
  • Share one credential set across multiple teams or environments
  • Log access tokens or secrets in application logs or error messages

7. Performance Tips

Small optimisations compound. Use the following patterns to get the most performance out of every API call.

Use pagination and field filtering+

Most list endpoints support page and pageSize parameters. Always paginate large result sets - fetching everything in one call is wasteful and slow. Where the API supports field selection, request only the fields your application uses. Smaller payloads are faster and cheaper on bandwidth.

GET /public/public-flights/v4/flights?page=0&pageSize=20
Set connection and read timeouts+

Always configure explicit timeouts on your HTTP client. Without them, a slow or hung response will block a thread indefinitely and exhaust your thread pool.

Recommended: 5 s connection timeout · 10 s read timeout. If your use case requires longer timeouts (e.g. large schedule downloads), set them explicitly and log slow responses for monitoring.

Reuse HTTP connections (Keep-Alive / connection pooling)+

Establishing a new TLS connection for every request adds 100-300 ms of overhead. Use an HTTP client that supports keep-alive and connection pooling. Most popular libraries handle this automatically when you reuse the client instance:

  • Python: use a requests.Session() object
  • Java: use WebClient or a shared OkHttpClient
  • Node.js: axios and node-fetch reuse connections by default

8. Testing & Environments

The ACC (Acceptance) environment mirrors the production configuration and is the best place to validate your integration before going live. ACC access is not automatic - it is not available to Public tier users and is not guaranteed for all Partner accounts either. You can request access and the Schiphol API team will review your situation.

Requesting ACC access

ACC is not available to Public tier users and is not automatically granted to all Partner accounts. If you need a test environment, contact the Schiphol API team with your use case and we will review your request. Internal tier users have ACC access by default.

ACC API basehttps://api-acc.schiphol.nl
ACC token endpointhttps://api-acc.auth.schiphol.nl/oauth/token
ACC Developer Portalhttps://developer-acc.schiphol.nl
PRD API basehttps://api.schiphol.nl
PRD token endpointhttps://api.auth.schiphol.nl/oauth/token
Never use production credentials in ACC

If you have ACC access, always create a separate application in the ACC Developer Portal with its own Client ID and Secret. Never reuse production credentials in a test environment.

What to test before going to production+

Run the following checklist before going live - against ACC if you have access, or with careful low-volume testing against production:

  • Token acquisition and automatic refresh works correctly
  • All expected HTTP status codes (200, 401, 403, 429, 500) are handled
  • Exponential backoff fires correctly on 429 and 5xx responses
  • Responses are parsed correctly for edge cases (empty arrays, null fields)
  • Timeouts are configured and do not hang the application
  • Caching returns fresh data and does not serve stale responses indefinitely
  • No secrets or tokens appear in application logs

What's next?