Migrating to the v2 API
EventSourcingDB 1.3 introduces the v2 API and deprecates the v1 API. The v1 API is supported until January 31, 2027, inclusive, and a later release removes it. This page explains what changes, route by route, so that you can move a client from v1 to v2.
Both APIs run side by side in 1.3, on the same port and the same data, with the same API token. You can therefore move one route at a time: what one client writes through v2, another reads through v1, and the other way round. Every response of the v1 API carries a Deprecation header and a Sunset header, so a proxy or a log shows which clients still use it.
If you use an official client SDK, update it to its version for EventSourcingDB 1.3, which speaks v2 by itself.
What Stays the Same#
The events do not change: a client writes and reads the same CloudEvents, with the same subjects, event types, and sources. Preconditions, EventQL, the options of reading and observing, such as bounds and fromLatestEvent, and the API token work as before.
What Changes Everywhere#
- Routes live below
/api/v2/<category>/<route>, in the categoriesdata,management, andsystem, and some of them have new names, as the table below shows. - Reads use
QUERYrather thanPOST, since they change nothing. A proxy, load balancer, or firewall in front of EventSourcingDB has to letQUERYthrough. - Every item of a response is an envelope whose content is in
data, where v1 named itpayload. A single answer, such as the one to reading an event type or to the diagnosis, is an envelope as well, where v1 sent it bare. - Every finite stream ends with an
enditem, and a stream that breaks off without it was cut. Every stream sendsheartbeatitems while it has nothing else to send, including writing events and registering a schema, whose answers are streams now. - Errors are reported in the response, with status
200and an item of the typeerrorthat names its code, rather than through status codes such as409or404. See Errors. - As in v1, a body has to have exactly the shape a route expects: a field the route does not know, or
nullwhere it takes none, is rejected with400, and the response names where it lies. A missing field or a wrong value, which v1 refused with400as well, is reported as an error item with status200. - A request to a route that takes a body has to name its content type in the
Content-Typeheader,application/json, orapplication/x-ndjsonfor restoring a backup or importing events, which parameters such ascharset=utf-8may follow. A request without it, or with another one, is rejected with400; v1 does not check it. - Answers that the server makes up itself, such as the one to ping, are CloudEvents in an envelope of the type
event.
Routes#
| v1 | v2 |
|---|---|
POST /api/v1/write-events |
POST /api/v2/data/write-events |
POST /api/v1/read-events |
QUERY /api/v2/data/read-events |
POST /api/v1/observe-events |
QUERY /api/v2/data/observe-events |
POST /api/v1/run-eventql-query |
QUERY /api/v2/data/run-eventql-query |
POST /api/v1/register-event-schema |
POST /api/v2/data/register-event-schema |
POST /api/v1/read-subjects |
QUERY /api/v2/data/read-subjects |
POST /api/v1/read-event-types |
QUERY /api/v2/data/read-event-types |
POST /api/v1/read-event-type |
QUERY /api/v2/data/read-event-type |
POST /api/v1/backup |
POST /api/v2/management/create-backup |
POST /api/v1/restore |
POST /api/v2/management/restore-backup |
POST /api/v1/verify-store |
POST /api/v2/management/verify-store |
POST /api/v1/audit-history |
POST /api/v2/management/audit-history |
POST /api/v1/put-into-read-only-mode |
POST /api/v2/management/enable-read-only-mode |
GET /api/v1/ping |
POST or GET /api/v2/system/ping |
GET /api/v1/health |
QUERY or GET /api/v2/system/health |
GET /api/v1/diagnostics |
POST /api/v2/system/diagnose |
POST /api/v1/verify-api-token |
POST /api/v2/system/verify-token |
GET /.well-known/openapi |
QUERY or GET /api/v2/system/openapi |
| – | POST /api/v2/management/import-events |
| – | QUERY /api/v2/management/export-events |
Importing and exporting events, as described in Export and Import, and incremental backups exist only in the v2 API; the v1 API has no counterpart for them.
What Changes per Route#
- Writing events answers with a stream of the written events, as items of the type
event, followed byend, rather than with a JSON array. The preconditions on a subject take an optionrecursive, which applies them to everything below the subject as well, see Checking a Subject and Everything Below It. - Listing subjects takes the subject as
subject, where v1 named itbaseSubject, and lists it alone unlessoptions.recursiveistrue. v1 always listed every subject below it as well, so setrecursivetotrueto get what v1 returned. - Listing event types and reading an event type name the type
type, where v1 named iteventType, and say whether it has events ashasEvents, which is the opposite of v1'sisPhantom. Reading an event type that the database does not know is reported asevent-type-unknownrather than with404. - Registering an event schema names the type
type, where v1 named iteventType, both in the request and in theevent-schema-registeredevent it answers with, which now comes in a stream. - Running an EventQL query sends each result as an item of the type
row, as v1 did, but holds the result indata.valuerather than inpayloaditself. A mistake in the query names its position, where it is known, in itsdetails, asstartandend, each with alineand acolumn. - Creating a backup takes
lastBackedUpEvent:nullfor a full backup, or theidandhashof the event a previous backup ended at for an increment that holds only what was written since. It answers in a format of its own, with abackupitem, theeventSchemaandeventitems, thelastBackedUpEventit ended at, andend, where v1 sentschemaandeventlines with nothing that marks the beginning or the end of the backup, see create-backup. Restoring a backup takes a full backup followed by its increments, and also a backup that v1 wrote. Like the v1 API of this version, it refuses a schema in such a backup that does not describe an object, which earlier versions accepted. See Backup and Restore. - Auditing the event store takes
lastAuditedEvent, theidandhashof the event a previous audit ended at, ornullfor a first audit, where v1 tooklatestEventIdandlatestEventHash. See Auditing the Event Store. - Verifying the store sends what it finds as items of the type
finding. - Enabling read-only mode answers with a
read-only-mode-enabledevent, and reportsread-only-mode-already-enabledif the mode is on already. - Ping answers with a
ping-receivedevent, and verifying the token with atoken-verifiedevent, where v1 named itapi-token-verified. - Health chooses OpenMetrics through the
Acceptheader, asapplication/openmetrics-text, rather than through?format=openmetrics, and answers a status offailwith503rather than500. Its metrics have new names, so queries and alerts that use those of v1 need rewriting. See Logging and Monitoring. - The diagnosis is a
POSTtodiagnoseand answers with aserver-diagnosedevent, which also holds the options that v1 leaves out. It gives the remaining time of the license and the heartbeat interval in seconds rather than nanoseconds, asremainingDurationInSecondsandheartbeatIntervalInSeconds, where v1 named themremainingDurationandheartbeatInterval, see Diagnostics and Troubleshooting. - The OpenAPI definition of v2 is JSON, where that of v1 is YAML. The API catalog at
/.well-known/api-cataloglists both.
Errors#
Where v1 answered with a status code, v2 answers with status 200 and an error item that names its code. A route that answers with a stream sends the error as an item of that stream. This section compares v2 with the v1 API of 1.3, which answers some cases differently than 1.2 did. The most common cases:
| v1 | v2 |
|---|---|
400 for a value that is missing or not valid, such as a malformed subject |
the code of that value, such as subject-malformed |
409 for a precondition that does not hold |
precondition-violated |
400 for an EventQL precondition that cannot be evaluated, a 500 in 1.2 |
query-malformed, query-unevaluable, or query-not-boolean |
400 for an EventQL query, run or in a precondition, that nests more than 256 levels or holds a chain of more than 10,000 operands |
query-too-deep |
400 for an EventQL precondition whose query is longer than 100 KiB |
query-too-long |
409 for an event that does not match the schema of its type |
event-schema-violated |
409 for an event whose type has no schema while schemas are required |
event-schema-unregistered |
400 for a schema that refers back to a part of itself or reaches more than 10,000 subschemas, and 409 for a write of an event type with such a stored schema |
event-schema-too-complex |
409 for a schema that is registered already |
event-schema-already-registered |
409 for events beyond the capacity of the license |
capacity-exceeded |
507 for a license that has expired |
license-invalid |
404 for an event type the database does not know |
event-type-unknown |
409 for a restore into a database that is not empty |
store-not-empty |
400 for a restore with an event that lies more than an hour beyond the clock of the server |
backup-ahead-of-clock |
503 for read-only mode, a full disk, a running restore, a failed store, or indexes that are rebuilt |
store-read-only, disk-full, restore-in-progress, store-failed, or indexes-rebuilding |
an error line in a stream, with a message |
an error item with a code, such as event-chain-broken or file-unreadable |
Some status codes stay, since they report a problem of the request or the server rather than of what it asks for: 400 for a body of the wrong shape, 401, 404 for a route that does not exist, 405, 408, 413, 429, 500, and 503 for a server that shuts down before it has answered. The overview of the errors lists every code.