Skip to main content

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:

  1. The URI base path MUST include a major version number prefixed with v. Example: https://example.org/v1, https://example.org/v2.
  2. The integration layer MUST send the full version number of the API in the API-Version header. 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:

StatusDescription
200The request has succeeded.
304The requested resource is not modified.
400The request is invalid.
404The requested resource does not exist.
406The request could not be accepted according to the content negotiation headers.
415The media type in the request is not supported.
429The server has received too many requests.
500A server error has occurred.
503The 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.

  1. A presentation layer MAY send the Accept header 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 (*/*).
  2. The integration layer MUST negotiate the media type from the list of media ranges in the Accept header. 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 a 406 Not Acceptable status code.
  3. If a presentation layer does not send the Accept header, the integration layer MUST send the content in its default media type.
  4. The integration layer MUST send the media type of its response in the Content-Type header. Its value MUST conform to the HTTP semantics.
  5. The integration layer MUST send the Vary: Accept header to indicate to a presentation layer that it supports content negotiation for media types. This tells a presentation layer that changing the value of the Accept header 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.

  1. The integration layer MUST support application/ld+json and application/json in the Accept header: a presentation layer MAY request either.
  2. If a presentation layer does not send the Accept header, or sends a media range that accepts application/ld+json (such as */* or application/*), the integration layer MUST serve JSON-LD.
  3. The integration layer MUST send Content-Type: application/ld+json when it serves JSON-LD, and Content-Type: application/json when the presentation layer requests application/json only.

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.

  1. A presentation layer MAY send the Accept-Language header 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 (*).
  2. The integration layer MUST negotiate the language from the list of language tags in the Accept-Language header. 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 a 406 Not Acceptable status code.
  3. If a presentation layer does not send the Accept-Language header, the integration layer MUST send its content in its default language.
  4. The integration layer MUST send the language tag of the content in the Content-Language header. Its value MUST conform to the HTTP semantics.
  5. 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-Language header values.
  6. The integration layer MUST send the Vary: Accept-Language header to indicate to a presentation layer that it supports content negotiation for languages. This informs a presentation layer that changing the value of the Accept-Language header in a request will yield a different, language-aware representation of a resource.
To be discussed

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.

  1. A presentation layer MAY send the Accept-Encoding header in its request to indicate which compression schemes it supports, such as gzip, br, deflate or zstd. Its value MUST conform to the HTTP semantics.
  2. If a presentation layer sends the Accept-Encoding header, the integration layer MAY compress the response body using one of the schemes the presentation layer supports. The integration layer MUST then send the Content-Encoding header to indicate which scheme it used. Its value MUST conform to the HTTP semantics.
  3. If a presentation layer does not send the Accept-Encoding header, or requests only schemes the integration layer does not support, the integration layer MUST send the response body uncompressed, without a Content-Encoding header.
  4. If the integration layer supports compression, it MUST send the Vary: Accept-Encoding header to indicate to a presentation layer that responses can differ based on the value of the Accept-Encoding request header. This informs a presentation layer that changing the value of the Accept-Encoding header 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.

  1. The integration layer SHOULD send the Cache-Control header, 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.
  2. The integration layer SHOULD send the ETag header with a unique fingerprint of a resource. When the resource changes, the integration layer MUST change its fingerprint.
  3. A presentation layer MAY send the If-None-Match header 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 the 304 Not Modified status code. Otherwise, the integration layer MUST return the modified resource with a new ETag header value.
  4. The integration layer SHOULD send the Last-Modified header with a timestamp of when the resource was last modified. Its value MUST conform to the HTTP semantics.
  5. A presentation layer MAY send the If-Modified-Since header in its request. If the resource has not been modified since that time, the integration layer MUST respond with the 304 Not Modified status code. Otherwise, the integration layer MUST return the modified resource with a new Last-Modified header value.
  6. If a presentation layer sends both the If-None-Match and If-Modified-Since headers in the same request, the integration layer MUST accept If-None-Match and ignore If-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.

  1. 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-Agent header.
  2. The integration layer SHOULD send the RateLimit-Policy and RateLimit headers to communicate its rate limiting policy and the current limits for a particular presentation layer.
  3. The integration layer SHOULD send a 429 Too Many Requests status code if a presentation layer has sent too many requests within the current time window.
  4. The integration layer SHOULD send the Retry-After header 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.

  1. A browser-based presentation layer MUST send the Origin header to indicate the origin (scheme, hostname, and optionally port) that caused the request to the integration layer.
  2. The integration layer MUST send the Access-Control-Allow-Origin header. 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 the Access-Control-Allow-Credentials: true header MUST be send.
  3. If the integration layer sends a response with an Access-Control-Allow-Origin value with an explicit origin (rather than the * wildcard), the integration layer MUST also send the Vary: Origin header to indicate to a presentation layer that responses can differ based on the value of the Origin request header.
  4. The integration layer MUST send the Access-Control-Allow-Methods header to specify which HTTP methods are permitted for cross-origin requests.
  5. The integration layer MUST send the Access-Control-Allow-Headers header to specify which HTTP headers are permitted for cross-origin requests.
  6. The integration layer MUST support the HTTP OPTIONS method and send the headers above in response to an OPTIONS request (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:

  1. The integration layer MUST return an appropriate HTTP status code, such as 404 or 500.
  2. The integration layer MUST return error information according to Problem Details for HTTP APIs. The error information MUST contain at least the following properties:
NameData typeCardinalityDescription
statusnumber1The HTTP status code. Example: 400, 404, 500.
titlestring1A 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.
detailstring1A human-readable explanation specific to this occurrence of the problem.
To be discussed

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.

To do

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.

  1. A presentation layer SHOULD send the User-Agent header 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.
  2. The integration layer MAY respond with a 400 Bad Request status code if the User-Agent header 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.