This document is licensed under
Creative Commons Attribution 4.0 International Public License
This module defines a standardized approach for performing batch operations in RESTful APIs. The objective of batching is to improve efficiency and scalability in situations where clients need to retrieve multiple resources in a single interaction.
The module specifies conventions for endpoint design, request and response formats, and error handling. By following these rules, API providers enable consistent client implementations, reduce network overhead, and improve overall performance and reliability in distributed environments.
This is a proposed version authored by the ADR "orchestration" working group. Comments regarding this document may be shared through GitHub: https://github.com/Geonovum/KP-APIs/issues
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY, MUST, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
This section is non-normative.
Batching provides a standardized way for clients to retrieve multiple resources in a single call, reducing network overhead and simplifying access patterns in distributed systems.
The design rules in this document address common challenges such as the N+1 problem, orchestration across distributed sources, and predictable request–response mapping. They are intended to complement, not replace, existing singular and collection endpoints, ensuring consistency, scalability, and ease of use across APIs.
This module defines rules for implementing batch operations in RESTful APIs. Batching is introduced as a complementary access pattern to singular and collection endpoints, designed to improve efficiency, scalability, and client usability.
The design rules specify when and how to expose batch endpoints, how to structure requests and responses, and how to handle errors consistently. By standardizing these aspects, batching ensures predictable client–server interactions, reduces the risk of divergent implementations, and promotes interoperability across APIs in distributed architectures.
The scope of this batching module is limited to synchronous read operations. It does not include write operations, transactional guarantees across multiple requests, or global batch endpoints spanning multiple collections. Each batch item is processed independently, and partial successes or failures are explicitly allowed and expected. Asynchronous patterns like 'Claim-Check', 'Subscribe-Notification' and 'Call-back' are also out of scope.
Batch endpoints are useful in situations where multiple related resources need to be retrieved in a single operation. Typical use cases include:
Orchestration across distributed data sources
Resources are often spread across multiple data sources, residing at different physical locations. A batch endpoint enables an application (or orchestration component) to perform distributed lookups efficiently, by reducing the complexity and client–server chatter.
For example: a client retrieves a list of persons from one source, which only contains address identifiers. Instead of performing a separate request per person to fetch the corresponding address, the client can submit a single batch request to the batch endpoint to retrieve all addresses at once.
Fetching multiple known resources
A client needs details for a set of known resource identifiers. Instead of issuing one request per identifier, the client can submit them in a single batch request, simplifying logic and reducing latency.
Parallel evaluation of collection filters
A reporting tool needs to query the same resource collection multiple times with different filter criteria (e.g. addresses in different postal code areas). A batch request allows all queries to be executed in parallel, returning the results in a single response.
This section is non-normative.
Batching is an architectural pattern that complements the standard RESTful model of singular and collection endpoints. Its purpose is to improve efficiency in distributed systems by reducing network overhead and providing predictable request–response mappings in situations where multiple resources need to be fetched at once.
Modern APIs often operate in a distributed data landscape. A single logical query may require access to multiple underlying systems or databases, each with its own identifiers, query capabilities, and performance characteristics. This distribution introduces challenges:
Batching mitigates these issues by grouping multiple requests into a single network call, enabling the server to optimize access to underlying systems and respond in a coordinated manner.
A common inefficiency in distributed APIs is the N+1 problem. For example, a client queries one collection to retrieve N entities, then performs N additional singular lookups to fetch related resources. The result is N+1 total calls, each incurring network and processing overhead.
Batch endpoints provide a standardized way to replace N+1 calls with just two calls: one to retrieve the initial collection and one batch request to fetch all related resources in parallel. This pattern reduces latency and lowers the load on both clients and servers.
Batching is a performance optimization, not a replacement for singular or collection endpoints.
Batching is not a transaction mechanism. Each request item in a batch is processed independently, and the success or failure of one item does not affect the others. There is no guarantee of atomicity, consistency, isolation, or durability (ACID) across items in a batch.
null.
items array.
This design ensures resilience: a batch MAY contain both successful and unsuccessful entries, but the overall response is always delivered in a predictable format.
The following principles guide the architecture of batch endpoints:
This batching architecture extends the architecture document of het NL API Strategy as published on: docs.geostandaarden.nl/api/API-Strategie-architectuur
This section is non-normative.
Batch endpoints provide a standardized way to retrieve multiple resources in a single operation. They are designed to reduce the number of network round-trips and to mitigate the N+1 problem, where a client would otherwise need to issue many separate requests for related resources. Batch endpoints are exposed per collection and follow a uniform request and response format, ensuring predictable behavior across APIs.
/batching/path: Expose a standardized batch endpoint for collections
Each collection resource that supports batch requests must
expose a dedicated batch endpoint. The path of the batch
endpoint must be constructed by appending the literal
/_batch to the path of the collection resource. The
batch endpoint must implement the POST method.
For the adressen collection, the batch endpoint
is:
POST /adressen/_batch
Which collections require batch support depends on the context and client use cases, and is left up to the API designer.
Using a fixed suffix (/_batch) makes batch
endpoints predictable and discoverable for clients. By scoping
the batch endpoint directly under the collection, the semantics
of the request are unambiguous: all items in the batch relate to
resources of that collection. Restricting the method to
POST ensures consistent support across frameworks
and avoids limitations of GET requests with bodies
or overly long query strings.
/adressen).
/_batch to the collection path (e.g.
POST /adressen/_batch).
200).
405 Method Not Allowed.
{collection}/_batch consistently across all
batch-enabled collections.
/batching/req-format: Use the standardized payload format for batch requests
A batch request must be sent as a JSON object containing a
top-level requests property, whose value must be an
ordered array of request items. Each entry in requests must be a
JSON object that specifies exactly one selection criterion for
that entry (see the specific rules for
singular and
collection requests). The
order of the requests items is significant and
preserved in the response.
The batch request must be submitted using the
POST method and content type
application/json.
An implementation-defined maximum applies to the number of
entries in requests. Requests that exceed this
maximum must be rejected.
The payload may include an additional top-level
context property alongside requests,
to provide contextual request criteria to apply on all request
items (such as time travelling).
For the adressen collection, the batch endpoint
is:
{
"context": {
"peildatum": "2025-09-12"
},
"requests": [
{ ... },
{ ... },
{ ... }
]
}
Using a standardized JSON envelope with a
requests property makes batch requests predictable
and uniform across all collections. It ensures that client and
server implementations can rely on a single pattern, reducing
ambiguity and implementation errors. The use of
POST with a request payload avoids limitations of
URL length and tooling support for GET request
bodies. The optional context property provides a
forward-compatible mechanism for passing shared criteria without
breaking the core structure.
requests array and preserve the intended order of
items.
context property allows
cross-cutting parameters to be added without introducing new
endpoints or breaking existing clients.
Content-Type: application/json and a JSON body
containing a requests array.
200.
requests property is rejected with status code
400.
requests array is handled appropriately (either
accepted with an empty results array, or rejected
per server policy).
context property,
validate that it can be included alongside
requests and affects the response as documented.
/batching/res-format: Use the standardized payload format for batch responses
A batch response must be sent as a JSON object containing a
top-level results property, whose value must be an
ordered array of result items. The number of items in
results must be exactly equal to the number of
items in the input requests array. The order of the
results items must be identical to the order of the
corresponding request items.
Each entry in results must correspond to exactly
one request item:
null if not found or not accessible.
items array, which may be empty if no
resources matched the criteria.
Servers may include additional top-level properties in the
response (such as metadata), provided they do not alter the
semantics of results.
Example response for a batch request combining a singular and a collection criterion:
{
"results": [
{
"identificatie": "12345",
"postcode": "1234AB",
"huisnummer": 1
},
{
"items": [
{
"identificatie": "34567",
"postcode": "3456AB",
"huisnummer": 3
}
]
}
]
}
Wrapping batch results in a results array
guarantees a predictable, deterministic mapping between requests
and responses. By enforcing equal length and order, clients can
align response entries with their original requests by array
index, without the need for correlation keys. Using
null or empty arrays distinguishes clearly between
"not found" and "no matches," improving client-side error
handling and reducing ambiguity.
results array of the
same length as requests, even if some entries are
null or empty.
results property.
requests array containing multiple items.
results array with exactly the same number of
items as the requests array.
results corresponds to the order of items in
requests.
null.
items array (which may be empty if no
resources matched).
results array
structure.
This section is non-normative.
Singular resource requests target one specific resource by its unique
identifier. This chapter defines how such requests are represented in
batch operations, using the key property to identify
resources consistently with their canonical URI. The rules ensure that
both simple and compound identifiers can be handled reliably in batch
requests and responses.
/batching/req-singular: Specify a list of singular requests when batching singular resources
When requesting a batch of singular resources, a list of singular requests must be specified.
Each singular request must contain at least the unique
identifier of the thing that the singular resource represents,
using the key property. In most cases, things are
identified by a single scalar value (string or number). However,
they could also be identified by a compound value, consisting of
multiple scalar values (in the resource URI, they would
typically be represented by multiple path segments).
An example request for requesting two building resources in a single batch operation:
// POST https://api.example.org/v1/gebouwen/_batch
{
"requests": [
{ "key": "3b9710c4-6614-467a-ab82-36822cf48db1" },
{ "key": "609b0651-acad-4091-9144-432621df8bf8" }
]
}
In case of a compound identifier, the value of the
key property must be an array, in the exact same
order as the path segments of the corresponding resource URI.
The value type of the key property must be
identical to the original value of the identifier. In case of a
compound identifier, this could differ per part.
When a singular resource has a compound identifier, an array value must be provided. In this example, the first identifier part is a string, and the second part is numeric.
// POST https://api.example.org/v1/gebouwen/_batch
{
"requests": [
{ "key": ["key-1-part-1", 123] },
{ "key": ["key-2-part-1", 456] }
]
}
Using a standardized key property for singular
requests ensures consistency across all batch-enabled
collections. It mirrors the way singular resources are addressed
in URIs, making the mapping intuitive for both clients and
server implementers. Supporting both scalar and compound
identifiers provides flexibility for resources that require
multi-part keys, while preserving strict typing and ordering
prevents ambiguity in request processing.
key property for
singular requests, with either a scalar or an ordered array
depending on the resource’s identifier model.
key property, and
reject requests with malformed
or incomplete identifiers.
/gebouwen/_batch) with a JSON body containing a
requests array, where each item has a
key property with a valid identifier.
200 is
returned.
results array with the same number of items as
the requests array.
key property is an array of values in the
correct order and with the correct data types.
400 Bad Request response.
/batching/res-singular: Return a list of singular results when batching singular resources
When returning the results of a batch of singular requests, the
response must contain a top-level results property.
The value of results must be an array with exactly
the same number of items, and in the same order, as the
requests array in the corresponding request.
Each entry in results must correspond to exactly
one request item:
null.
Example response for two building resources, one found and one missing:
{
"results": [
{
"identificatie": "3b9710c4-6614-467a-ab82-36822cf48db1",
"naam": "Stadhuis",
"bouwjaar": 1978
},
null
]
}
Returning a results array of equal length and order
ensures deterministic mapping between request and response
items. Using null to represent missing or
inaccessible resources simplifies client-side error handling and
reduces ambiguity. Additionally, returning null for
both non-existent and unauthorized resources prevents clients
from inferring whether a resource exists but is protected, or
simply does not exist. This consistent response pattern
mitigates potential information disclosure risks.
results array of
the same length as the request, even when some entries are
null.
null entries gracefully,
treating them as "resource not found" or "user not
authorized".
key property).
results array with exactly the same number of
items as the requests array.
null.
null is returned for resources the
user is not authorized to access (indistinguishable from
non-existing resources).
This section is non-normative.
Collection resource requests allow clients to retrieve multiple
resources that match a set of filter criteria. In batch operations,
these requests are expressed with a standardized
filter object. This chapter describes how such requests are
structured, how results are returned, and how empty matches are
represented. The rules align with existing collection query semantics
while ensuring deterministic mapping between requests and responses.
/batching/req-collection: Specify a list of collection requests when batching collection resources
When requesting a batch of collection resources, a list of collection requests must be specified.
Each collection request must contain a
filter property, whose value is an object
containing one or more filter criteria. These criteria must
correspond to valid query parameters or attributes defined for
the collection resource. A combination of multiple filter
criteria in a single request is allowed, if the collection
request supports it.
The structure of the filter object must follow the
same typing rules as the underlying resource attributes,
ensuring that values match the expected data types. An empty
result set must be returned when no resources match the given
filter criteria.
Example request when requesting two address collection resources in a single batch operation:
// POST https://api.example.org/v1/adressen/_batch
{
"requests": [
{ "filter": { "postcode": "1234AB", "huisnummer": 1 }},
{ "filter": { "postcode": "3456XY", "huisnummer": 32 }}
]
}
Using a standardized filter object for collection
requests ensures consistency with existing collection endpoints,
where filtering is already a core concept. This approach allows
multiple sets of filter criteria to be evaluated in a single
request, reducing the number of round-trips and eliminating the
need for clients to reassemble paginated or mixed results
manually. It also enables flexible querying while keeping batch
requests predictable and self-contained.
filter object for each
collection request, with criteria that are valid for the
targeted collection resource.
results array, containing an
items array of zero or more matching resources.
items array rather than omitting the result.
/adressen/_batch) with a JSON body containing a
requests array, where each item has a
filter object with valid filter criteria.
200 is
returned.
results array with the same number of items as
the requests array.
items array (which may be empty if no resources
match).
400 Bad Request response.
/batching/res-collection: Return a list of collection results when batching collection resources
When returning the results of a batch of collection requests,
the response must contain a top-level
results property. The value of
results must be an array with exactly the same
number of items, and in the same order, as the
requests array in the corresponding request.
Each entry in results must correspond to exactly
one collection request and must be a JSON object containing an
items property. The value of
items must be an array of zero or more resource
objects that match the specified filter criteria.
If no resources match a filter, the corresponding
items array must be empty. The result entry itself
must never be omitted.
Pagination of items within a result entry is
currently not standardized. Implementations that support
pagination may include additional fields (e.g.
nextToken or nextLink) in the result
entry. Such extensions must not alter the semantics of
items and must be optional for clients to consume.
Example response for two address collection requests, where the first returns matches and the second none:
{
"results": [
{
"items": [
{
"identificatie": "12345",
"postcode": "1234AB",
"huisnummer": 1
},
{
"identificatie": "67890",
"postcode": "1234AB",
"huisnummer": 2
}
]
// optional, implementation-specific
"nextToken": "eyJwYWdlIjoxfQ=="
},
{
"items": []
}
]
}
Standardizing collection results as objects with an
items array ensures predictable and uniform
handling across all batch-enabled collections. It distinguishes
clearly between “no results” (items is empty) and
“request missing” (not allowed). Preserving array length and
order maintains deterministic mapping between request filters
and result sets. Optional pagination fields allow flexibility
for large collections without constraining implementations to a
single standard prematurely.
items is empty.
items arrays; servers may
enforce limits per
batch to protect performance.
items without using pagination extensions.
filter object).
results array with exactly the same number of
items as the requests array.
results is a JSON
object containing an items property.
items array (not null or omitted).
This section is non-normative.
Batch endpoints must use the standardized Problem Details
(problem+json) [rfc9457] format to represent errors. Problem details provide a
machine-readable structure while still being human-readable, ensuring
interoperability and consistency across APIs.
Errors are reported using an HTTP error status and a
problem+json body. Item-level errors within a batch
response must not trigger a batch-level error but must be represented in
the results array.
/batching/err-req-invalid: Reject invalid batch requests using problem+json
If a batch request is malformed, contains invalid JSON, or fails
schema validation, the server must reject it with status code
400 Bad Request and return a
problem+json body.
// HTTP/1.1 400 Bad Request
// Content-Type: application/problem+json
{
"type": "https://api.example.org/problems/invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "The request payload is missing the required property 'requests'."
}
Explicitly rejecting invalid requests ensures clients receive clear feedback on how to correct errors, instead of undefined or partial behavior. Using the standardized problem+json format promotes interoperability across APIs.
400 with a
problem+json response.
requests property and validate that the server
returns status code 400.
type, title,
status, and detail.
Content-Type header of the
error response is application/problem+json.
/batching/err-req-limit: Reject batch requests that exceed the maximum number of items
If the number of entries in requests exceeds the
server-defined maximum, the server must reject the request with
status code 400 Bad Request and return a
problem+json body.
// HTTP/1.1 400 Bad Request
// Content-Type: application/problem+json
{
"type": "https://api.example.org/problems/request-limit-exceeded",
"title": "Request limit exceeded",
"status": 400,
"detail": "Batch requests are limited to 100 items, but 114 items were submitted."
}
Exceeding the maximum number of request items is a client-side
input error, not a payload overflow. Returning
400 Bad Request clarifies that the client must
correct the request, while still using the standardized
problem+json format for machine-readable feedback.
requests array containing more items than the
maximum allowed.
400 with a problem+json response.
200).
/batching/err-res-limit: Reject batch requests that would produce oversized responses
If fulfilling a batch request would result in a response
exceeding server-defined limits (for example, too many results
or an oversized payload), the server must reject the request
with status code 400 Bad Request and return a
problem+json body.
// HTTP/1.1 400 Bad Request
// Content-Type: application/problem+json
{
"type": "https://api.example.org/problems/response-limit-exceeded",
"title": "Response limit exceeded",
"status": 400,
"detail": "The response would exceed the maximum allowed size of 10 MB."
}
Requests that would result in an oversized response represent
invalid client input rather than an infrastructure failure.
Using 400 Bad Request makes it clear that the
client must adjust filters or batch size. The standardized
problem+json body provides actionable details.
400 with a
problem+json response when the limit would be
exceeded.
/batching/err-invalid-keys: Reject singular batch requests with invalid keys
If a singular batch request contains invalid keys (for example,
an invalid UUID), the server must reject the entire batch with
status code 400 Bad Request and return a
problem+json body. The response must indicate which
keys caused the failure.
// HTTP/1.1 400 Bad Request
// Content-Type: application/problem+json
{
"type": "https://api.example.org/problems/invalid-keys",
"title": "Invalid keys",
"status": 400,
"detail": "One or more provided keys are invalid.",
"invalidKeys": [
"not-a-uuid"
]
}
Invalid keys represent client input errors. Rejecting the entire batch avoids returning mixed results that could confuse clients and ensures that request validation is strict and predictable. Including the list of failing keys helps clients identify and correct errors efficiently.
invalidKeys array or equivalent field to indicate
which keys failed, enabling corrective actions.
"not-a-uuid").
400 with a problem+json response.
invalidKeys array).
200 (with null for non-existing
resources in the results).
This section is non-normative.
A number of widely used APIs and standards support batching. Approaches vary along several axes: endpoint scope (per-collection vs global), payload shape (JSON envelope vs multipart/mixed), operation types (read-only vs mixed read/write), result mapping (position vs correlation IDs), and error semantics (uniform result vs per-subrequest HTTP response).
Elasticsearch exposes a per-index and global
_mget endpoint that retrieves multiple documents by ID in
a single call. Clients submit an array of document identifiers in the
request payload and receive a result per requested identifier. The
model is read-only, index-scoped by default, and result order
corresponds to the request order.
Example request payload:
{
"docs": [
{
"_id": "1"
},
{
"_id": "2"
}
]
}
OData defines a global $batch endpoint that can combine
heterogeneous operations (reads and writes) into a single HTTP
request. Historically, batching used media type
multipart/mixed; OData v4 additionally defines a JSON
batch request/response format as part of the [OData JSON Format]. Responses contain per-subrequest status, headers, and bodies,
effectively embedding multiple HTTP exchanges in one envelope.
Ordering and dependencies can be expressed.
Example request payload (JSON batch format):
{
"requests": [
{
"id": "1",
"method": "get",
"url": "https://example.org/api/buildings/1"
},
{
"id": "2",
"method": "get",
"url": "https://example.org/api/buildings/2"
}
]
}
Several Google APIs accept batch requests as a single HTTP call with
media type multipart/mixed, where each part is a nested
HTTP request. This enables combining multiple heterogeneous
operations; responses mirror the embedded HTTP results. The format
prioritizes transport flexibility but is more complex to implement
efficiently than a pure-JSON envelope.
The working group analyzed the standards described above and made deliberate trade-offs to optimize for simplicity, predictability, and the specific needs of Dutch government APIs.
| Aspect | Adopted from | How it is applied |
|---|---|---|
| Per-collection endpoint | Elasticsearch |
Batch endpoints are scoped per collection (/_batch
suffix), not global. This keeps semantics clear and avoids
mixing unrelated resources.
|
JSON envelope with requests array |
OData |
The request payload uses a top-level
requests property containing an ordered array,
similar to OData's JSON batch format.
|
| Positional result mapping | Elasticsearch | Results are returned in the same order as the requests, allowing clients to map responses by array index without correlation IDs. |
| Aspect | Present in | Reason for exclusion |
|---|---|---|
| Global batch endpoint | OData, Google APIs | A single global endpoint that can address any resource increases complexity and reduces predictability. Per-collection endpoints are simpler to implement, document, and secure. |
| Multipart/mixed format | OData, Google APIs |
The multipart/mixed format embeds full HTTP
requests and responses, adding parsing complexity. A pure JSON
format is easier to implement, debug, and consume in modern
client libraries.
|
| Per-subrequest HTTP semantics | OData, Google APIs |
Embedding HTTP status codes and headers per subrequest adds
overhead and complexity. This module uses
null for missing resources and a separate error
response for batch-level failures, reducing ambiguity.
|
| Correlation IDs | OData | Requiring clients to generate and match correlation IDs adds implementation burden. Positional mapping (by array index) achieves the same goal more simply. |
| Mixed read/write operations | OData, Google APIs | Supporting heterogeneous operations (GET, POST, PUT, DELETE) in a single batch increases transactional complexity and error handling. This module focuses on read-only batching. |
| Dependency expressions | OData | OData allows expressing dependencies between subrequests (e.g., use the result of request 1 in request 2). This adds significant complexity and is not needed for the read-only use case. |
The working group also evaluated collection filtering via query parameters as an alternative to a dedicated batch endpoint. Batch support could be implemented by passing a series of criteria as filters to an existing collection endpoint. However, this approach has significant drawbacks compared to the standards above:
A dedicated batch endpoint with a JSON request body avoids these issues while providing the same benefits as Elasticsearch and OData.