# export-events

Exports **every event** of the database as **plain CloudEvents**, one per line, exactly as **[`import-events`](/docs/eventsourcingdb/api/import-events)** takes them.

- **Route:** `QUERY /api/v2/management/export-events`
- **Category:** `management`
- **Authentication:** required, with the API token
- **Answer:** a stream of NDJSON, without envelopes

An export is the **mirror of an import**: what it writes, an import takes without conversion, and keeps the ID and the time of every event, so that you can move the events into another database, or remove personal data from them on the way, as **[GDPR Compliance](/docs/eventsourcingdb/gdpr-compliance)** describes. Unlike **[`read-events`](/docs/eventsourcingdb/api/read-events)**, it sends each event **without an envelope** and **without its hashes and its signature**, which an import computes anew, and it always sends **every event**, since an import takes the IDs from `0` on without gaps, so that only a full export imports. To read some of the events, use `read-events`. An export holds no schemas: **[`read-event-types`](/docs/eventsourcingdb/api/read-event-types)** lists them, to register them in the database you import into.

An export holds the **events written up to its start**. **Writes go on while it runs**, and what they add is not part of it.

## Request

The route takes **no body** and **no options**. To export every event into a file:

```shell
curl \
  -X QUERY \
  -H "authorization: Bearer secret" \
  http://localhost:3000/api/v2/management/export-events \
  -o events.ndjson
```

## Answer

The answer has the content type `application/x-ndjson`, and holds **one event per line**, in the order in which the events were written, with these fields, in this order:

| Field | Value |
| --- | --- |
| `specversion` | The spec version of the event, `1.0`. |
| `id` | The ID of the event. |
| `source` | The **[source](/docs/eventsourcingdb/sources)** of the event. |
| `subject` | The **[subject](/docs/eventsourcingdb/subjects)** of the event. |
| `type` | The **[type](/docs/eventsourcingdb/event-types)** of the event. |
| `time` | The time of the event. |
| `datacontenttype` | The content type of the data of the event, `application/json`. |
| `data` | The data of the event. |
| `traceparent` | The traceparent of the event, where it has one; otherwise, the field is left out. |
| `tracestate` | The tracestate of the event, where it has one; otherwise, the field is left out. |

Every field is written **as the database holds it**. A database without events is answered with an **empty body**, which an import takes as well.

For a database with two events, the request above is answered with:

```json
{"specversion":"1.0","id":"0","source":"https://library.eventsourcingdb.io","subject":"/books/42","type":"io.eventsourcingdb.library.book-acquired","time":"2026-11-05T09:00:00Z","datacontenttype":"application/json","data":{"title":"2001 – A Space Odyssey","author":"Arthur C. Clarke"}}
{"specversion":"1.0","id":"1","source":"https://library.eventsourcingdb.io","subject":"/books/42","type":"io.eventsourcingdb.library.book-borrowed","time":"2026-11-05T09:00:00Z","datacontenttype":"application/json","data":{"borrowedBy":"/readers/23"},"traceparent":"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}
```

Unlike every other stream of the API, an export has **no `end`, `error`, or `heartbeat` items**, since every line is an event that an import takes. So it tells whether it is complete through the connection alone:

- **An error after the first event cuts the connection.** The answer then breaks off rather than ending cleanly: over HTTP/1.1, the client sees the transfer end before its last chunk, as `curl` reports with `transfer closed with outstanding read data remaining`, and over HTTP/2, the stream is reset. The server logs the cause.
- **A long pause can meet the idle timeout of a proxy.** An export sends no heartbeats, so where the database takes long for the next events, as for a large event that is read from a slow disk, a proxy between the client and the database may close the connection for being idle, and the answer breaks off as well. Raise the idle timeout of such a proxy for this route, or export where no proxy stands in between.

### Checking That an Export Is Complete

A client that keeps an export should check that it got all of it:

1. **Treat an answer that breaks off as failed**, rather than keeping what arrived: a client library reports an error for a connection that is cut, and `curl` exits with an error code other than `0`.
2. **Compare the export with the database.** The IDs of an export begin at `0` and follow each other without gaps, so the number of its lines is the ID of its last line plus one. Read the latest event before the export, for example with **[`read-events`](/docs/eventsourcingdb/api/read-events)** on the subject `/` with the options `recursive` set to `true` and `order` set to `antichronological`, taking only its first event: the export holds at least that event, and every one before it.
3. **Import into an empty database** as a last check: an import refuses IDs with gaps and lines that are cut within their JSON.

### Events That Do Not Import Unchanged

An export writes every event as the database holds it, and an import checks every event **as a write checks it today**. A database that took events before today's checks, from an earlier release or from a backup made by hand, can hold events that an import refuses, such as a **source that is no URI**, a **subject beyond today's bounds**, a `specversion` other than `1.0`, a `datacontenttype` other than `application/json`, or a `tracestate` without a `traceparent`, which W3C Trace Context does not allow, and which a restore took nonetheless. The export of such a database **does not import unchanged**: the import reports the first such event, and the line has to be changed before it imports.

## Errors

An export carries no `error` items. An error **before the first event** is answered **with status `200` and a single error envelope**, with the content type `application/json`, rather than with the stream:

- **[`restore-in-progress`](/docs/eventsourcingdb/errors/restore-in-progress)**: A restore or an import is running, which the export does not wait for at its start. Once it has begun, an export waits for a restore instead.
- **[`store-failed`](/docs/eventsourcingdb/errors/store-failed)**: The store refuses reads after a restore or an import that could not empty it again.
- **[`file-unreadable`](/docs/eventsourcingdb/errors/file-unreadable)**: A file of the store that the export reads is damaged, such as an events file whose checksum no longer matches.

For example, while a restore runs:

```json
{
  "type": "error",
  "data": {
    "code": "https://docs.eventsourcingdb.io/errors/restore-in-progress",
    "description": "restore is in progress: try again once it has finished",
    "details": {
      "operation": "read"
    }
  }
}
```

An error **after the first event**, such as a damaged file or the server shutting down, **cuts the connection**, as described above.

## Status Codes

Before the first event, the server answers with a status code other than `200` where it cannot take the request at all, as described in **[Errors](/docs/eventsourcingdb/api#errors)**:

- **`401`**: The API token is missing or wrong.
- **`429`**: Too many requests were sent, or the client holds too many open streams; the answer says when to try again. An export counts as a stream, as creating a backup does.
- **`500`**: The server failed unexpectedly before the first event. After it, the connection is cut instead.
- **`503`**: The server is shutting down, or began to shut down before the first event.
