General rules
Introduction
An API provides an interface for presentation layers to interact with an integration layer, decoupling them from specific underlying systems and technologies. The API of an integration layer must adhere to several rules to ensure consistent and predictable interactions.
Documentation
The API is as good as the accompanying documentation. The documentation of the API must be easily discoverable, searchable and publicly accessible. It is often the primary resource for developers of presentation layers during implementation.
API documentation MUST be provided in the form of an OpenAPI definition document which conforms to the OpenAPI Specification, version 3 or later.
See Documentation in the API Design Rules for more information.
Versioning
The API may evolve over time. It MUST be versioned as follows:
- The URI base path MUST include a major version number prefixed with
v. Example:https://example.org/v1,https://example.org/v2. - The integration layer MUST send the full version number of the API in the
API-Versionheader. Example:API-Version: 1.2.3,API-Version: 2.1.0.
Example
An example request from a presentation layer:
GET /v1/entities/1234
Host: example.org
This tells the integration layer that the presentation layer is requesting a resource of version 1 (v1) of the API.
An example of the response headers of the integration layer:
HTTP/2 200 OK
API-Version: 1.2.3
The response indicates that the exact version of the API is 1.2.3.
The integration layer MUST facilitate the transition between versions. For example, the integration layer SHOULD publish a deprecation schedule and a changelog. See Versioning in the API Design Rules for more information.
Status codes
The integration layer MUST use common HTTP status codes in its responses: 2xx for success, 3xx for redirects, 4xx for errors caused by the presentation layer and 5xx for errors caused by the integration layer.
The following table lists common status codes:
| Status | Description |
|---|---|
200 | The request has succeeded. |
304 | The requested resource is not modified. |
400 | The request is invalid. |
404 | The requested resource does not exist. |
406 | The request could not be accepted according to the content negotiation headers. |
415 | The media type in the request is not supported. |
429 | The server has received too many requests. |
500 | A server error has occurred. |
503 | The server is under maintenance. |
Media types
The integration layer must use media types to enable open and extensible content negotiation. The rules of this section apply to every response of an integration layer, whatever the media type of its content, such as JSON data, binary files or a CSV for download. This section lists the main requirements — see the HTTP Semantics for more information.
- A presentation layer MAY send the
Acceptheader in its request. Its value MUST conform to the HTTP semantics. For example, the value may contain a specific media type (e.g.application/json), a list of media types (e.g.application/json, application/problem+json) or any media type (*/*). - The integration layer MUST negotiate the media type from the list of media ranges in the
Acceptheader. If one or more of the requested media types are supported, the integration layer MUST select the most preferred supported media type and send its content in that media type. If none of the requested media types are supported, the integration layer MUST respond with a406 Not Acceptablestatus code. - If a presentation layer does not send the
Acceptheader, the integration layer MUST send the content in its default media type. - The integration layer MUST send the media type of its response in the
Content-Typeheader. Its value MUST conform to the HTTP semantics. - The integration layer MUST send the
Vary: Acceptheader to indicate to a presentation layer that it supports content negotiation for media types. This tells a presentation layer that changing the value of theAcceptheader in a request will yield a different representation of a resource.
JSON-LD
The API uses JSON by default: JSON is easy to parse and is supported natively in most programming languages. The integration layer serialises this JSON as JSON-LD 1.1, a format for linked data. JSON-LD is the default media type of the API. Because JSON-LD is JSON, a presentation layer that only knows JSON can parse a JSON-LD response like any other JSON document.
- The integration layer MUST support
application/ld+jsonandapplication/jsonin theAcceptheader: a presentation layer MAY request either. - If a presentation layer does not send the
Acceptheader, or sends a media range that acceptsapplication/ld+json(such as*/*orapplication/*), the integration layer MUST serve JSON-LD. - The integration layer MUST send
Content-Type: application/ld+jsonwhen it serves JSON-LD, andContent-Type: application/jsonwhen the presentation layer requestsapplication/jsononly.
Example
An example request from a JSON-LD-aware presentation layer:
GET /v1/entities/1234
Host: example.org
Accept: application/ld+json
This tells the integration layer that the presentation layer prefers the response to be serialised as JSON-LD.
An example of the response headers of the integration layer:
HTTP/2 200 OK
Content-Type: application/ld+json
Vary: Accept
The response indicates that the body is serialised as JSON-LD.
An example request from a conventional presentation layer that only knows JSON:
GET /v1/entities/1234
Host: example.org
Accept: application/json
An example of the response headers of the integration layer:
HTTP/2 200 OK
Content-Type: application/json
Vary: Accept
Both responses carry the same body. A JSON-LD-aware presentation layer interprets the body as linked data. A conventional presentation layer treats the body as plain JSON.
Languages
Heritage information is available in one or more languages, such as Dutch or English. The integration layer must allow presentation layers to request information in a preferred language, even if the integration layer only exposes information in one language. This section lists the main requirements — see the HTTP Semantics for more information.
- A presentation layer MAY send the
Accept-Languageheader in its request to indicate which language it prefers. Its value MUST conform to the HTTP semantics. For example, the value may contain a specific language tag (e.g.nl), a list of language tags (e.g.nl, en) or any language (*). - The integration layer MUST negotiate the language from the list of language tags in the
Accept-Languageheader. If one or more of the requested languages are supported, the integration layer MUST select the most preferred supported language and send its content in that language. If none of the requested languages are supported, the integration layer MUST respond with a406 Not Acceptablestatus code. - If a presentation layer does not send the
Accept-Languageheader, the integration layer MUST send its content in its default language. - The integration layer MUST send the language tag of the content in the
Content-Languageheader. Its value MUST conform to the HTTP semantics. - The integration layer MUST NOT serve its content in different languages in one response. Instead, a presentation layer MUST issue separate requests for each language, with different
Accept-Languageheader values. - The integration layer MUST send the
Vary: Accept-Languageheader to indicate to a presentation layer that it supports content negotiation for languages. This informs a presentation layer that changing the value of theAccept-Languageheader in a request will yield a different, language-aware representation of a resource.
How can a presentation layer discover the supported languages? Should we add a separate discovery endpoint (e.g. /v1/languages, returning a collection of languages)? Or should a presentation layer rely upon the OpenAPI definition, documenting the languages? The first one makes sense if we treat languages as 'data'. The second one makes sense if we treat languages as 'documentation'.
Example
An example request from a presentation layer:
GET /v1/entities/1234
Host: example.org
Accept-Language: nl
This tells the integration layer that the presentation layer prefers the content to be in Dutch.
An example of the response headers of the integration layer:
HTTP/2 200 OK
Content-Language: nl
Vary: Accept-Language
The response indicates that the content is in Dutch and that a new request to the same resource with a different Accept-Language header value will result in a different representation of the resource.
Another example request from a presentation layer:
GET /v1/entities/1234
Host: example.org
Accept-Language: de, nl
This tells the integration layer that the presentation layer prefers the content to be in German, otherwise in Dutch.
An example of the response headers of the integration layer:
HTTP/2 200 OK
Content-Language: nl
Vary: Accept-Language
The response indicates that the content is in Dutch: German is not supported by the integration layer.
Character encoding
Character encoding defines how characters are converted into bytes by the integration layer for transmission to a presentation layer. The integration layer MUST encode all payload responses using UTF-8, except for payloads that are binary by nature, such as images.
Compression
Compression reduces the size of a response body as it is transmitted to a presentation layer, improving performance and reducing bandwidth use. The integration layer SHOULD support compression. This section lists the main requirements — see the HTTP Semantics for more information.
- A presentation layer MAY send the
Accept-Encodingheader in its request to indicate which compression schemes it supports, such asgzip,br,deflateorzstd. Its value MUST conform to the HTTP semantics. - If a presentation layer sends the
Accept-Encodingheader, the integration layer MAY compress the response body using one of the schemes the presentation layer supports. The integration layer MUST then send theContent-Encodingheader to indicate which scheme it used. Its value MUST conform to the HTTP semantics. - If a presentation layer does not send the
Accept-Encodingheader, or requests only schemes the integration layer does not support, the integration layer MUST send the response body uncompressed, without aContent-Encodingheader. - If the integration layer supports compression, it MUST send the
Vary: Accept-Encodingheader to indicate to a presentation layer that responses can differ based on the value of theAccept-Encodingrequest header. This informs a presentation layer that changing the value of theAccept-Encodingheader in a request will yield a differently compressed representation of a resource.
Example
An example request from a presentation layer:
GET /v1/entities/1234
Host: example.org
Accept-Encoding: gzip, br
This tells the integration layer that the presentation layer can handle the compressed response body with either gzip or br.
An example of the response headers of the integration layer:
HTTP/2 200 OK
Content-Encoding: br
Vary: Accept-Encoding
The Content-Encoding: br header indicates that the body is compressed with Brotli (br). The Vary: Accept-Encoding header indicates that a new request to the same resource with a different Accept-Encoding header value will result in a differently compressed representation of the resource.
Caching
Caching is a mechanism where presentation layers store responses from the integration layer to reuse them for subsequent requests. The integration layer SHOULD support caching via HTTP headers; it enhances performance by reducing server load and latency. This section lists the main requirements — see HTTP Caching for more information.
- The integration layer SHOULD send the
Cache-Controlheader, to make clear to a presentation layer whether information can be cached and, if so, for how long (e.g. 1 hour, 1 day or 1 week). Its value MUST conform to the HTTP Caching standard. - The integration layer SHOULD send the
ETagheader with a unique fingerprint of a resource. When the resource changes, the integration layer MUST change its fingerprint. - A presentation layer MAY send the
If-None-Matchheader in its request with one or more ETag values returned by the integration layer. If the resource still has one of the provided ETags, the integration layer MUST respond with the304 Not Modifiedstatus code. Otherwise, the integration layer MUST return the modified resource with a newETagheader value. - The integration layer SHOULD send the
Last-Modifiedheader with a timestamp of when the resource was last modified. Its value MUST conform to the HTTP semantics. - A presentation layer MAY send the
If-Modified-Sinceheader in its request. If the resource has not been modified since that time, the integration layer MUST respond with the304 Not Modifiedstatus code. Otherwise, the integration layer MUST return the modified resource with a newLast-Modifiedheader value. - If a presentation layer sends both the
If-None-MatchandIf-Modified-Sinceheaders in the same request, the integration layer MUST acceptIf-None-Matchand ignoreIf-Modified-Since.
Example
An example of the response headers of the integration layer:
HTTP/2 200 OK
Cache-Control: max-age=3600
ETag: "xyz"
Last-Modified: Fri, 28 Aug 2026 04:58:08 GMT
The response indicates that this resource can be cached for 1 hour (max-age=3600) without checking the integration layer, has ETag xyz, and was last modified on Friday 28 August 2026.
Rate limiting
Rate limiting is a traffic control mechanism that caps the number of requests a presentation layer can make to the integration layer within a specific time window. It protects the integration layer's infrastructure from overload and abuse. This section lists the main requirements — see Retry-After and RateLimit header fields for HTTP for more information.
- The integration layer SHOULD support rate limiting. It chooses a policy that best fits its situation. For example, the integration layer may choose a rate limiting algorithm such as Token Bucket, Fixed Window Counter or Sliding Window. The integration layer may also choose a method for identifying presentation layers, for example based on IP address or the
User-Agentheader. - The integration layer SHOULD send the
RateLimit-PolicyandRateLimitheaders to communicate its rate limiting policy and the current limits for a particular presentation layer. - The integration layer SHOULD send a
429 Too Many Requestsstatus code if a presentation layer has sent too many requests within the current time window. - The integration layer SHOULD send the
Retry-Afterheader if a presentation layer has sent too many requests within the current time window, to indicate how long the presentation layer ought to wait before making a new request.
Example
An example of the response headers of the integration layer:
HTTP/2 200 OK
RateLimit-Policy: "default";q=100;w=60
RateLimit: "default";r=50;t=30
The RateLimit-Policy indicates that a presentation layer may send up to 100 requests (q=100) per 60 seconds (w=60). The RateLimit shows 50 of those remain (r=50) and that they may be used within the next 30 seconds (t=30).
An example of the response headers of the integration layer when a rate limit has been reached:
HTTP/2 429 Too Many Requests
Retry-After: 120
The response indicates that a presentation layer has made too many requests and that it can try again after 120 seconds.
Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is a mechanism that allows browser-based presentation layers to interact with the integration layer. The integration layer MUST support CORS by following the relevant requirements of the CORS specification. This section lists the main requirements.
- A browser-based presentation layer MUST send the
Originheader to indicate the origin (scheme, hostname, and optionally port) that caused the request to the integration layer. - The integration layer MUST send the
Access-Control-Allow-Originheader. The value SHOULD be*to allow access to the integration layer from any origin. If the integration layer restricts access, a specific origin MUST be provided instead of*, and theAccess-Control-Allow-Credentials: trueheader MUST be send. - If the integration layer sends a response with an
Access-Control-Allow-Originvalue with an explicit origin (rather than the*wildcard), the integration layer MUST also send theVary: Originheader to indicate to a presentation layer that responses can differ based on the value of theOriginrequest header. - The integration layer MUST send the
Access-Control-Allow-Methodsheader to specify which HTTP methods are permitted for cross-origin requests. - The integration layer MUST send the
Access-Control-Allow-Headersheader to specify which HTTP headers are permitted for cross-origin requests. - The integration layer MUST support the HTTP
OPTIONSmethod and send the headers above in response to anOPTIONSrequest (the preflight request pattern).
Example
An example of a preflight request from a presentation layer:
OPTIONS /v1/entities/1234
Host: example.org
Origin: https://mywebsite.nl
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Accept, Accept-Language, If-None-Match, If-Modified-Since, Origin
An example of the preflight response headers of the integration layer:
HTTP/2 200 OK
Content-Length: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Accept, Accept-Language, If-None-Match, If-Modified-Since, Origin
Access-Control-Max-Age: 7200
An example of a regular, non-preflight request from a presentation layer:
GET /v1/entities/1234
Host: example.org
Origin: https://mywebsite.nl
An example of the regular, non-preflight response headers of the integration layer:
HTTP/2 200 OK
Access-Control-Allow-Origin: *
Error handling
When an error occurs, the integration layer MUST handle errors as follows:
- The integration layer MUST return an appropriate HTTP status code, such as
404or500. - The integration layer MUST return error information according to Problem Details for HTTP APIs. The error information MUST contain at least the following properties:
| Name | Data type | Cardinality | Description |
|---|---|---|---|
status | number | 1 | The HTTP status code. Example: 400, 404, 500. |
title | string | 1 | A short, human-readable summary of the problem type. For example: Resource not found for every resource that could not be retrieved by the integration layer. |
detail | string | 1 | A human-readable explanation specific to this occurrence of the problem. |
Add support for the type property, for machine-readable processing?
Example
An example of the response headers of the integration layer:
HTTP/2 404 Not Found
Content-Type: application/problem+json
Content-Language: en
Note that the integration layer has responded with the application/problem+json media type, even though the presentation layer may not have listed it in its Accept header. This is allowed by the HTTP Semantics. Also note the Content-Language header, to make clear that the content is in a particular language, per the Accept-Language header of the presentation layer or, if that header was not provided, the default language of the integration layer.
An example of the response body of the integration layer:
{
"status": 404,
"title": "Resource not found",
"detail": "No heritage object found with ID 1234"
}
Note that the title is generic (it applies to every resource) and that the detail is specific (it applies to a particular resource with a particular ID).
Open access
The integration layer SHOULD be open to any presentation layer without technical constraints, such as authentication (e.g. via the Authorization header) or IP address filtering.
Access should only be restricted to designated presentation layers under specific circumstances, such as legal requirements. This specification does not dictate which technical constraints an integration layer should implement; that decision rests with the integration layer based on its specific needs.
Rephrase or remove this section — it's not yet clear what the requirements are.
Client identification
The integration layer should be able to monitor the usage of its API and advise presentation layers in optimising their implementations. The integration layer should therefore be able to identify individual presentation layers.
- A presentation layer SHOULD send the
User-Agentheader in its requests. The header value SHOULD consist of the name of the system of the presentation layer, the version of its system and the URL of the owner of the presentation layer. The value SHOULD look like this:system/version (url), e.g.MyApp/1.7.6 (https://mymuseum.nl). See the HTTP semantics for more information. - The integration layer MAY respond with a
400 Bad Requeststatus code if theUser-Agentheader in the request is missing or invalid.
Example
An example request from a presentation layer:
GET /v1/entities/1234
Host: example.org
User-Agent: MyApp/1.7.6 (https://mymuseum.nl)
This tells the integration layer that the request comes from system MyApp, version 1.7.6, operated by a presentation layer with URL https://mymuseum.nl.