read-events
Reads the events of a subject, or of a subject together with every subject below it.
- Route:
QUERY /api/v2/data/read-events - Category:
data - Authentication: required, with the API token
- Answer: a stream of NDJSON
A read returns the events of a subject, oldest first unless you ask for the reverse order, and lets you narrow them down by their IDs or start at the latest event of a type. It reads the events up to the latest one at the moment it starts: events written while it runs are not part of it. To receive those as well, observe the subject with observe-events. The tutorial Reading Events walks through it step by step.
Request#
The body is a JSON object, with the content type application/json:
| Field | Type | Required | Meaning |
|---|---|---|---|
subject |
string | yes | The subject to read, a slash-separated path such as /books/42. |
options.recursive |
boolean | no | Whether to read the events of every subject below the given one as well. Defaults to false, which reads the given subject alone. |
options.order |
string | no | chronological for the oldest event first, or antichronological for the newest first. Defaults to chronological. |
options.lowerBound |
object | no | The lowest event ID to read. Without it, the range begins at the first event. |
options.upperBound |
object | no | The highest event ID to read. Without it, the range ends at the latest event. |
options.lowerBound.id, options.upperBound.id |
string | yes, in a bound | An event ID, a non-negative integer in a string, without a sign or a leading zero, such as "23". |
options.lowerBound.type, options.upperBound.type |
string | yes, in a bound | inclusive to read the event with that ID as well, or exclusive to leave it out. |
options.fromLatestEvent |
object | no | Starts the read at the latest event of a type in a subject, that event included, instead of at lowerBound. |
options.fromLatestEvent.subject |
string | yes, in fromLatestEvent |
The subject of that event, exactly that one and not a subject below it. |
options.fromLatestEvent.type |
string | yes, in fromLatestEvent |
The event type of that event. |
options.fromLatestEvent.ifEventIsMissing |
string | yes, in fromLatestEvent |
What to read if there is no such event: read-everything reads as if fromLatestEvent were left out, read-nothing reads no events. |
The bounds select a range of event IDs, whichever order you read them in; see Reading a Specific Range. fromLatestEvent cannot be combined with lowerBound or with the order antichronological. It lets a read start at the latest snapshot of a subject, see Reading From the Last Event of a Given Type.
To read the events of /books/42:
curl \
-X QUERY \
-H "authorization: Bearer secret" \
-H "content-type: application/json" \
-d '{"subject":"/books/42"}' \
http://localhost:3000/api/v2/data/read-events
Answer#
The answer is a stream of items, one per line. Each event is an event item:
| Type | Data | When |
|---|---|---|
event |
the event, a CloudEvent with every field it was stored with | For every event that the read finds, in the order that options.order asks for. |
error |
code, description, and details |
When the read cannot go on, see Errors. |
heartbeat |
empty | While nothing else is sent for a while, to keep the connection alive. |
end |
empty | Once, as the last item, when the stream ends cleanly. |
Heartbeats and the end item work as on every stream of the API, see Streams. A subject without events, a range beyond the latest event, and a fromLatestEvent with read-nothing that finds no event are answered with the end item alone.
For a database in which /books/42 holds a book-acquired and a book-borrowed event, the request above is answered with:
{"type":"event","data":{"specversion":"1.0","id":"0","source":"https://library.eventsourcingdb.io","subject":"/books/42","type":"io.eventsourcingdb.library.book-acquired","time":"2026-10-28T04:36:03.031127Z","predecessorhash":"0000000000000000000000000000000000000000000000000000000000000000","datacontenttype":"application/json","data":{"author":"Arthur C. Clarke","isbn":"978-0756906788","title":"2001 – A Space Odyssey"},"traceparent":null,"tracestate":null,"hash":"esdb:hash:v2:0c93c8369291dbce3e786d0427a0b5e94a7be9867d5d7e7357f5b2557ec049cc","signature":null}}
{"type":"event","data":{"specversion":"1.0","id":"1","source":"https://library.eventsourcingdb.io","subject":"/books/42","type":"io.eventsourcingdb.library.book-borrowed","time":"2026-10-28T04:36:07.365416Z","predecessorhash":"esdb:hash:v2:0c93c8369291dbce3e786d0427a0b5e94a7be9867d5d7e7357f5b2557ec049cc","datacontenttype":"application/json","data":{"borrowedBy":"/readers/23","borrowedUntil":"2026-11-22"},"traceparent":null,"tracestate":null,"hash":"esdb:hash:v2:f3785555ef047f8366866f6b1a9da3e1967c0a911ff030779789ae1c5ef5ac88","signature":null}}
{"type":"end","data":{}}
Errors#
The read reports these errors as an error item, followed by the end item:
subject-missing: The request leaves out the subject.subject-malformed: The subject to read, or the subject offromLatestEvent, is not a valid subject, such asbooks/42without its leading slash.subject-too-long: The subject to read, or the subject offromLatestEvent, is longer than 1,024 characters.subject-too-deep: The subject to read, or the subject offromLatestEvent, has more than 32 segments.order-unknown:options.orderis neitherchronologicalnorantichronological.event-id-missing: A bound leaves out itsid.event-id-malformed: Theidof a bound is not a non-negative integer without a sign or a leading zero, such as-1or007.bound-type-missing: A bound leaves out itstype.bound-type-unknown: Thetypeof a bound is neitherinclusivenorexclusive.from-latest-event-incomplete:fromLatestEventleaves out itssubject, itstype, orifEventIsMissing.if-event-is-missing-unknown:ifEventIsMissingis neitherread-everythingnorread-nothing.event-type-malformed: ThetypeoffromLatestEventis not a valid event type.selection-conflicting: The request combinesfromLatestEventwithlowerBound, or with the orderantichronological.range-unsatisfiable: The bounds leave no event that could match, such as alowerBoundabove theupperBound, or the event thatfromLatestEventstarts at lies above theupperBound.restore-in-progress: A restore is running, which refuses reads until it has finished.file-unreadable: A file of the store that the read needs is damaged, such as an events file whose checksum no longer matches.store-failed: The store refuses reads for good, after a restore that could not empty it again.server-shutting-down: The server began to shut down while the read was running. Before the stream begins, the server answers with503instead.
Status Codes#
Before the stream begins, the server answers with a status code other than 200 where it cannot take the request at all, as described in Errors:
400: The content type of the body is notapplication/json, or the body is no JSON object of the shape above, for example with a field the route does not know, such asrecursiveoutside ofoptions, with a value of the wrong JSON type, such as anidthat is a number rather than a string, or withnullas a value.401: The API token is missing or wrong.408: The body did not arrive in time.413: The body is larger than its limit.429: Too many requests were sent, or the client already holds 256 open streams, see Rate Limiting; the answer says when to try again.500: The server failed unexpectedly before the stream began. Once it has begun, the stream breaks off without itsenditem instead.503: The server is shutting down.