Docs / EventSourcingDB / Reference / Migrating to the v2 API

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 categories data, management, and system, and some of them have new names, as the table below shows.
  • Reads use QUERY rather than POST, since they change nothing. A proxy, load balancer, or firewall in front of EventSourcingDB has to let QUERY through.
  • Every item of a response is an envelope whose content is in data, where v1 named it payload. 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 end item, and a stream that breaks off without it was cut. Every stream sends heartbeat items 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 200 and an item of the type error that names its code, rather than through status codes such as 409 or 404. See Errors.
  • As in v1, a body has to have exactly the shape a route expects: a field the route does not know, or null where it takes none, is rejected with 400, and the response names where it lies. A missing field or a wrong value, which v1 refused with 400 as well, is reported as an error item with status 200.
  • A request to a route that takes a body has to name its content type in the Content-Type header, application/json, or application/x-ndjson for restoring a backup or importing events, which parameters such as charset=utf-8 may follow. A request without it, or with another one, is rejected with 400; 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 by end, rather than with a JSON array. The preconditions on a subject take an option recursive, 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 it baseSubject, and lists it alone unless options.recursive is true. v1 always listed every subject below it as well, so set recursive to true to get what v1 returned.
  • Listing event types and reading an event type name the type type, where v1 named it eventType, and say whether it has events as hasEvents, which is the opposite of v1's isPhantom. Reading an event type that the database does not know is reported as event-type-unknown rather than with 404.
  • Registering an event schema names the type type, where v1 named it eventType, both in the request and in the event-schema-registered event 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 in data.value rather than in payload itself. A mistake in the query names its position, where it is known, in its details, as start and end, each with a line and a column.
  • Creating a backup takes lastBackedUpEvent: null for a full backup, or the id and hash of 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 a backup item, the eventSchema and event items, the lastBackedUpEvent it ended at, and end, where v1 sent schema and event lines 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, the id and hash of the event a previous audit ended at, or null for a first audit, where v1 took latestEventId and latestEventHash. 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-enabled event, and reports read-only-mode-already-enabled if the mode is on already.
  • Ping answers with a ping-received event, and verifying the token with a token-verified event, where v1 named it api-token-verified.
  • Health chooses OpenMetrics through the Accept header, as application/openmetrics-text, rather than through ?format=openmetrics, and answers a status of fail with 503 rather than 500. Its metrics have new names, so queries and alerts that use those of v1 need rewriting. See Logging and Monitoring.
  • The diagnosis is a POST to diagnose and answers with a server-diagnosed event, 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, as remainingDurationInSeconds and heartbeatIntervalInSeconds, where v1 named them remainingDuration and heartbeatInterval, see Diagnostics and Troubleshooting.
  • The OpenAPI definition of v2 is JSON, where that of v1 is YAML. The API catalog at /.well-known/api-catalog lists 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.