# Writing Events

This guide explains how to **store events** in EventSourcingDB using its **HTTP API**. At the end, you will have successfully written your first events.

> **Before You Begin**
>
> All examples on this page and the following ones assume that you have a **running EventSourcingDB instance** and a **valid API token**. If not, see **[Running EventSourcingDB](/docs/eventsourcingdb/running-eventsourcingdb)**.
>
> Whenever a `curl` command contains **`<API_TOKEN>`**, replace it with your actual token. Otherwise, EventSourcingDB will reject the request due to failed authentication.
>
> No further configuration is required – all examples work out of the box with the default setup.

## Modeling an Event

Before you can store an event, you first need to think about what the event should represent. This depends on your business domain, as the **event's meaning** is tightly coupled to it.

For example, if you're building an online shop, you might have an `order submitted` event. If you're developing a chess server, perhaps a `knight moved` event. If you're managing a library, a `book acquired` event. We'll use the library example in this guide because it's easy to follow, includes some interesting aspects, and might even feel familiar.

For now, we'll go with the `book acquired` event because acquiring a book logically happens at the beginning – otherwise, the library wouldn't have any books.

So, you already know that the **type** of your event should be **`book acquired`**. Since EventSourcingDB is based on the **[CloudEvents](/docs/eventsourcingdb/cloud-events)** standard, event types must be specified as **reverse domain names** – for example, **`io.eventsourcingdb.library.book-acquired`**. For more details, see **[Event Types](/docs/eventsourcingdb/event-types)**.

The CloudEvents standard requires a few additional fields:

- The **source** (the application that emits the event)
- The **subject** (this is essentially the object the events refer to)
- The actual **data**, containing the event-specific business payload

Let's assume that your application is intended to run under `https://library.eventsourcingdb.io`. Then the source would simply be this URL. For more details, see **[Sources](/docs/eventsourcingdb/sources)**.

Let's also assume that you assign a random ID to the newly acquired book, e.g. `42`. The subject could then look like this: `/books/42`. For more details, see **[Subjects](/docs/eventsourcingdb/subjects)**.

The data is a standard JSON object containing the book's details:

```json
{
  "title": "2001 – A Space Odyssey",
  "author": "Arthur C. Clarke",
  "isbn": "978-0756906788"
}
```

That gives us everything we need to construct the event:

```json
{
  "source": "https://library.eventsourcingdb.io",
  "subject": "/books/42",
  "type": "io.eventsourcingdb.library.book-acquired",
  "data": {
    "title": "2001 – A Space Odyssey",
    "author": "Arthur C. Clarke",
    "isbn": "978-0756906788"
  }
}
```

> **Events vs. Event Candidates**
>
> Technically, events contain additional fields, such as the timestamp when the event was created and the version of the CloudEvents specification. However, these fields are automatically added by EventSourcingDB when the event is stored.
>
> Internally, what you send to the database is called an **event candidate**. It's not an actual event yet – it's more like a proposal to *become* one. Only when the database accepts and stores the candidate does it turn into a **real event**.

## Storing an Event

To store the event, you send a **`POST` request** to EventSourcingDB's **`/api/v1/write-events`** endpoint. You can do this from the command line using **`curl`**. Make sure to include the **API token** for authentication that was set when starting the database:

```shell
curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"events\": [
      {
        \"source\": \"https://library.eventsourcingdb.io\",
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-acquired\",
        \"data\": {
          \"title\": \"2001 – A Space Odyssey\",
          \"author\": \"Arthur C. Clarke\",
          \"isbn\": \"978-0756906788\"
        }
      }
    ]
  }" \
  http://localhost:3000/api/v1/write-events
```

> **If Something Goes Wrong**
>
> If you receive an error stating that the **connection is not authorized**, check whether the API token was set correctly. You need to **replace `<API_TOKEN>`** in the example with your actual token.

## Understanding the Response

If everything worked as expected, the server will respond with **HTTP status code `200 OK`**. The first line should look like this:

```
HTTP/1.1 200 OK
```

You can ignore the other headers.

What's more interesting is the **response payload**. It looks like this, with the exception of the dynamically generated `time` field:

```json
[
  {
    "specversion": "1.0",
    "id": "0",
    "time": "...",
    "source": "https://library.eventsourcingdb.io",
    "subject": "/books/42",
    "type": "io.eventsourcingdb.library.book-acquired",
    "datacontenttype": "application/json",
    "data": {
      "title": "2001 – A Space Odyssey",
      "author": "Arthur C. Clarke",
      "isbn": "978-0756906788"
    },
    "predecessorhash": "0000000000000000000000000000000000000000000000000000000000000000",
    "hash": "...",
    "signature": null
  }
]
```

> **About `predecessorhash`, `hash`, and `signature`**
>
> EventSourcingDB adds cryptographic hashes and, optionally, signatures to each event to ensure integrity and authenticity:
>
> - The `predecessorhash` field contains the hash of the previous event in the event store, or all zeroes if this is the very first event.
> - The `hash` field is a cryptographic fingerprint of the current event. It is used to ensure data integrity.
> - The `signature` field indicates whether the event is cryptographically signed. For now, you can ignore this field – more details will follow later in the documentation.
>
> For details on how hashes and signatures work in EventSourcingDB, see **[Verifying Event Signatures](/docs/eventsourcingdb/verifying-event-signatures)**.

## Writing Multiple Events at Once

You may have noticed that the parameter in the `curl` example was called **`events` (plural)**, not `event` (singular), and that the event was wrapped in an array.

That's because you can send multiple events in a single request. EventSourcingDB handles all events in a request as part of a **transaction**, guaranteeing **atomicity** – meaning either all events are written, or none.

## Using Preconditions

Sometimes you only want to store events under certain conditions – for example, only if no events for a subject exist yet, or only if no new events have been written since the last read, or only if a specific EventQL query evaluates to `true`.

To support this, EventSourcingDB lets you define **preconditions** along with the events. For more details see [Preconditions](/docs/eventsourcingdb/preconditions).

> **Transactions and Preconditions**
>
> EventSourcingDB will only store the submitted events if *all* specified preconditions are met. This preserves the atomicity of the operation.

### Using `isSubjectPristine`

To ensure that events are only written if the subject has no existing events, use the `isSubjectPristine` precondition. This is useful when an event is meant to *introduce* a subject, like the `book-acquired` event above – it must be the **first event** and **must not appear multiple times** for the same subject.

If you resend the request as shown above, another event will be added to subject `/books/42`. But if you include an `isSubjectPristine` precondition, EventSourcingDB will reject the write – which in this case is what you want:

```shell
curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"events\": [
      {
        \"source\": \"https://library.eventsourcingdb.io\",
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-acquired\",
        \"data\": {
          \"title\": \"2001 – A Space Odyssey\",
          \"author\": \"Arthur C. Clarke\",
          \"isbn\": \"978-0756906788\"
        }
      }
    ],
    \"preconditions\": [
      {
        \"type\": \"isSubjectPristine\",
        \"payload\": {
          \"subject\": \"/books/42\"
        }
      }
    ]
  }" \
  http://localhost:3000/api/v1/write-events
```

The response will include HTTP status code `409 Conflict`:

```
HTTP/1.1 409 Conflict
```

> **Double-Check the Subject**
>
> Make sure you define the precondition for the correct subject. Technically, the precondition doesn't have to match the subject of the events being written, but that's typically what you want.

### Using `isSubjectPopulated`

The `isSubjectPopulated` precondition is the **opposite of `isSubjectPristine`**. It ensures that the subject **already has at least one event** – useful when you want to update or modify an existing subject.

For example, if you want to borrow a book, the book must have been acquired first. Using `isSubjectPopulated` prevents writing events to subjects that haven't been initialized yet:

```shell
curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"events\": [
      {
        \"source\": \"https://library.eventsourcingdb.io\",
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-borrowed\",
        \"data\": {
          \"borrowedBy\": \"/readers/23\",
          \"borrowedUntil\": \"2025-11-17\"
        }
      }
    ],
    \"preconditions\": [
      {
        \"type\": \"isSubjectPopulated\",
        \"payload\": {
          \"subject\": \"/books/42\"
        }
      }
    ]
  }" \
  http://localhost:3000/api/v1/write-events
```

If the book hasn't been acquired yet (subject is pristine), the request will fail with **HTTP status code `409 Conflict`**. Once you've written the `book-acquired` event first, the borrow operation will succeed.

### Using `isSubjectOnEventId`

The `isSubjectOnEventId` precondition also ensures that the subject already contains events – i.e., it is *not* empty.

In this case, you must specify both the subject and the ID of the most recent event. This lets you check that no new events have been added since you last read the subject – essentially enabling **optimistic locking** on a **per-subject basis**.

The following example tries to borrow a book from the library – which should only be allowed if the last known event was the acquisition of the book and no other events (like another borrow) have occurred since:

```shell
curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"events\": [
      {
        \"source\": \"https://library.eventsourcingdb.io\",
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-borrowed\",
        \"data\": {
          \"borrowedBy\": \"/readers/23\",
          \"borrowedUntil\": \"...\"
        }
      }
    ],
    \"preconditions\": [
      {
        \"type\": \"isSubjectOnEventId\",
        \"payload\": {
          \"subject\": \"/books/42\",
          \"eventId\": \"0\"
        }
      }
    ]
  }" \
  http://localhost:3000/api/v1/write-events
```

Run this immediately after acquiring the book, and it will succeed. If you repeat the request, it will fail – confirming that the optimistic locking worked as intended.

### Using `isEventQlQueryTrue`

Sometimes, you want to ensure that an event is only written **if a more complex condition holds** – for example, if no similar event has ever been recorded before. The `isEventQlQueryTrue` precondition lets you define such conditions using **[EventQL](/docs/eventsourcingdb/eventql)**.

In the following example, we make sure that no other book with the same title already exists in the database. If a book titled *2001 – A Space Odyssey* has already been acquired before, the write will be rejected:

```shell
curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"events\": [
      {
        \"source\": \"https://library.eventsourcingdb.io\",
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-acquired\",
        \"data\": {
          \"title\": \"2001 – A Space Odyssey\",
          \"author\": \"Arthur C. Clarke\",
          \"isbn\": \"978-0756906788\"
        }
      }
    ],
    \"preconditions\": [
      {
        \"type\": \"isEventQlQueryTrue\",
        \"payload\": {
          \"query\": \"FROM e IN events WHERE e.data.title == '2001 – A Space Odyssey' PROJECT INTO COUNT() == 0\"
        }
      }
    ]
  }" \
  http://localhost:3000/api/v1/write-events
```

As with other preconditions, the response will include HTTP status code `409 Conflict` if the condition is not met.

> **Arbitrary Conditions**
>
> The `isEventQlQueryTrue` precondition gives you full control over whether events should be written, based on the current state of the database. You can use any valid EventQL query that returns a single boolean value.

## Your Turn

So far, we've only written events for a single book. But if you look at the `book borrowed` event, you'll notice that it references a reader with the ID `/readers/23`. That reader doesn't exist yet – so let's fix that.

As an exercise, try modeling and writing two new events:

1. A **`reader applied` event**, indicating that someone has applied for a library card. This event should contain the reader's first and last name as data – for example:

    ```json
    {
      "firstName": "Jane",
      "lastName": "Doe"
    }
    ```

2. A **`reader accepted` event**, confirming that the reader has been approved and their library card has been issued. This event doesn't carry any domain-specific data – it should contain an **empty object as payload**:

    ```json
    {}
    ```

Write the two events in two separate `POST` requests – one per event. Make sure to use the appropriate preconditions:

- Use `isSubjectPristine` when writing the `reader applied` event to ensure that the subject doesn't exist yet.
- Use `isSubjectOnEventId` when writing the `reader accepted` event to make sure it follows the application and that no other events have been written in the meantime.

You can use `/readers/23` as the subject to match the `book borrowed` event above – or pick a different ID if you prefer.
