This document describes principles and patterns for the SDEP API.
Table of contents
Keep the API as simple and concise as possible.
REST APIs are one of the most common kinds of web interfaces available today.
Therefore, it's very important to design REST APIs properly so that we won't run into problems down the road.
Otherwise, we create problems for clients that use our APIs, which isn’t pleasant and detracts people from using our API.
If we don’t follow commonly accepted conventions, then we confuse the maintainers of the API and the clients that use them since it’s different from what everyone expects.
https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/
| # | Decision | Motivation/example |
|---|---|---|
| API 01 | Support OpenAPI 3.1.0 | Swagger 2.0 is legacy - https://swagger.io/specification/ |
| API 02 | All endpoints are self-explanatory/well-documented | |
| API 03 | Use nouns instead of verbs | Best practice - https://logius-standaarden.github.io/API-Design-Rules/ |
| API 04 | Use plurals for resources that affect collections | Best practice - https://logius-standaarden.github.io/API-Design-Rules/ |
| API 05 | Consistent datamodel | Avoid code duplication, only have Activity, Area, consider adding an attribute to indicate "non-reporting records" for CA |
| API 06 | Consistent endpoints | Have POST/GET "mirrors": POST /ca/areas, GET /str/areas; POST /str/activities, GET /ca/activities |
| API 07 | Consistent pagination | Have offset and limit for all endpoints with (potential) many records |
| API 08 | Syntax validation | Example: postal code |
| API 09 | Semantical validation | Example: begin timestamp < end timestamp |
| API 10 | Integrity validation | Example: can only submit activities for existing areas |
| API 11 | Single record POST (default) | To avoid transaction performance issues and to keep the endpoints simple (as opposed to bulk updates) |
| API 11b | Bulk POST (complement) | For high-volume STR platforms; available at /str/activities/bulk |
| API 12 | Logical ordering => readability | For POST, request and response follow the same ordering, extra data in response is moved to the end |
| API 13 | Essentiality | Example: in POST activities, only areaId and competentAuthorityId, but no competentAuthorityName |
| API 14 | Essentiality/security | Example: in POST activities request, no need to include platformId |
| API 15 | Consistent HTTP response codes | See HTTP status codes below |
| API 16 | STR and CA: manage area change | Areas may change over time, SDEP only administrates the changes and exposes the latest "truth" |
| HTTP Status | Meaning | When |
|---|---|---|
| 200 | OK | GET request completed successfully; bulk POST with partial success |
| 201 | Created | POST request created a new resource |
| 204 | No Content | DELETE request completed successfully (e.g. deactivate area) |
| HTTP Status | Meaning | When |
|---|---|---|
| 400 | Bad Request | Invalid query parameters on a GET request (e.g. offset=-1 or limit=abc), or missing client credentials |
| 401 | Unauthorized | Missing, invalid, or expired authentication token; missing required token claims (client_id, client_name) |
| 403 | Forbidden | Authenticated but missing a required role (sdep_ca, sdep_str, sdep_read, sdep_write) |
| 404 | Not Found | Requested resource does not exist, is unavailable, or has been deleted |
| 409 | Conflict | Duplicate resource (unique constraint violation) |
| 422 | Unprocessable Content | Invalid request body on a POST request (e.g. missing required field), or business rule violation (e.g. start time > end time) |
| HTTP Status | Meaning | When |
|---|---|---|
| 500 | Internal Server Error | Unexpected condition that prevented fulfilling the request (catch-all) |
| 503 | Service Unavailable | Database or authorization server (Keycloak) temporarily unavailable |
For the mapping between application exceptions and HTTP status codes, see Exception Handling in the Architecture document.
For production use in your own country, the utilization of a separate API gateway can be considered (on top of the SDEP API).
In SDEP-NL, a separate API gateway it not utlized right now, unless there are concrete edge-control requirements that cannot be handled by the existing ingress/reverse proxy setup.
- The API already acts as a functional gateway for data exchange (clear boundaries, OAuth2 client credentials, and role separation between
strandca). - The exposed endpoints are mainly transactional (
POST /str/activities, bulk ingest, area upload/download), so common gateway benefits like response caching are limited. - Adding another gateway layer introduces additional complexity: extra latency, policy duplication risk, configuration drift, and another operational failure domain.
- A simpler and stronger baseline is one hardened edge (load balancer/ingress + WAF + TLS termination), with authorization and business rules enforced in the app and identity provider.
Introduce one when at least one of these becomes a real requirement:
- Per-client quota/rate limiting and burst controls at larger platform scale.
- Centralized security/policy enforcement across multiple backend services (JWT claim rules, IP allowlists, mTLS, schema checks).
- API product capabilities (developer portal, key lifecycle, detailed usage analytics by client, monetization).
- Multi-service backend exposure with a single stable external contract.
No by default; yes conditionally.
Use architectural simplicity until specific non-functional requirements justify the additional gateway layer.
For production use in your own country, the utilization of a separate API gateway can be considered (on top of the SDEP API).
Within SDEP NL, a dedicated API gateway is currently not used. This is a deliberate choice based on how the platform is designed and operated. Only when specific edge-control requirements arise that cannot be handled by the existing ingress/reverse proxy setup should an additional gateway be introduced.
In context of SDEP NL:
-
SDEP NL already provides clear API boundaries
The SDEP API itself acts as a functional gateway for data exchange, with well-defined domains (strvsca), OAuth2 client credential flows, and strict role separation. -
Workload is primarily transactional, not cache-driven
Typical interactions (e.g.POST /str/activities, bulk ingestion, area upload/download) are write-heavy or data-exchange oriented, limiting the value of traditional API gateway features like response caching. -
Existing edge setup is sufficient and controlled
A hardened edge (ingress/reverse proxy + TLS termination) already provides the necessary entry point security and routing without introducing additional layers. -
Operational simplicity is a key design principle
Avoiding an extra gateway reduces:- latency in the request path
- duplication of security and routing policies
- risk of configuration drift
- an additional operational and failure domain
-
Authorization is intentionally handled at the right layers
Identity and access control are enforced via the identity provider and the application itself, aligning with SDEP’s architecture rather than shifting logic to an external gateway.
Introducing a gateway could become relevant when concrete needs arise, such as:
- Platform-scale rate limiting or quota management per client
- Need for API product capabilities (developer portal, client onboarding, usage analytics)
SDEP NL prioritizes a simple, robust edge architecture. A dedicated API gateway should only be introduced when clear non-functional requirements outweigh the added complexity.