Introduction

Overview

EventStoreDB provides a native interface of AtomPub over HTTP. AtomPub is a RESTful protocol that can reuse many existing components, for example reverse proxies and a client's native HTTP caching. Since events stored in EventStoreDB are immutable, cache expiration can be infinite. EventStoreDB leverages content type negotiation and you can access appropriately serialised events can as JSON or XML according to the request headers.

Compatibility with AtomPub

EventStoreDB v5 is fully compatible with the 1.0 version of the Atom Protocolopen in new window. EventStoreDB adds extensions to the protocol, such as headers for control and custom rel links.

WARNING

The latest versions of EventStoreDB (v20+) have the AtomPub protocol disabled by default. We do not advise creating new applications using AtomPub as we plan to deprecate it. Please explore our new gRPC protocol available in v20. It provides more reliable real-time event streaming with wide range of platforms and language supported.

Existing implementations

Many environments have already implemented the AtomPub protocol, which simplifies the process.

LibraryDescription
NET (BCL)System.ServiceModel.SyndicationServices
JVMhttp://java-source.net/open-source/rss-rdf-toolsopen in new window
PHPhttp://simplepie.org/open in new window
Rubyhttps://github.com/cardmagic/simple-rssopen in new window
Clojurehttps://github.com/scsibug/feedparser-cljopen in new window
Pythonhttp://code.google.com/p/feedparser/open in new window
node.jshttps://github.com/danmactough/node-feedparseropen in new window

WARNING

These are not officially supported by EventStoreDB.

Content types

The preferred way of determining which content type responses EventStoreDB serves is to set the Accept header on the request. As some clients do not deal well with HTTP headers when caching, appending a format parameter to the URL is also supported, for example, ?format=xml.

The accepted content types for POST requests are:

  • application/xml
  • application/vnd.eventstore.events+xml
  • application/json
  • application/vnd.eventstore.events+json
  • text/xml

The accepted content types for GET requests are:

  • application/xml
  • application/atom+xml
  • application/json
  • application/vnd.eventstore.atom+json
  • text/xml
  • text/html
  • application/vnd.eventstore.streamdesc+json

Appending events

You append to a stream over HTTP using a POST request to the resource of the stream. If the stream does not exist then the stream is implicitly created.

EventStoreDB media types

EventStoreDB supports a custom media type for posting events, application/vnd.eventstore.events+json or application/vnd.eventstore.events+xml. This format allows for extra functionality that posting events as application/json or application/xml does not. For example it allows you to post multiple events in a single batch.

The format represents data with the following jschema (eventId must be a UUID).

[
    {
      "eventId"    : "string",
      "eventType"  : "string",
      "data"       : "object",
      "metadata"   : "object"
    }
]
1
2
3
4
5
6
7
8

Appending a single event to a new stream

If you issue a POST request with data to a stream and the correct content type set it appends the event to the stream, and generates a 201 response from the server, giving you the location of the event. Using the following event, which you can also download as a file:

[
  {
    "eventId": "fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e4",
    "eventType": "event-type",
    "data": {
      "a": "1"
    }
  }
]
1
2
3
4
5
6
7
8
9

POST the following request to create a stream and add an event to it:

curl -i -d "@event.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json"
    -u "admin:changeit"
1
2
3
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:17:59 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10

Some clients may not be able to generate a unique identifier (or may not want to) for the event ID. You need this ID for idempotence purposes and EventStoreDB can generate it for you.

If you leave off the ES-EventId header you see different behavior:

curl -i -d "@event.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/json" \
    -H "ES-EventType: SomeEvent"
1
2
3
HTTP/1.1 307 Temporary Redirect
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Location: http://127.0.0.1:2113/streams/newstream/incoming/8a00e469-3a99-4517-a0b0-8dc662ffad9b
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 24 Jul 2018 14:42:44 GMT
Content-Length: 28
Keep-Alive: timeout=15,max=100
Forwarding to idempotent URI%
1
2
3
4
5
6
7
8
9
10
11
12

In this case EventStoreDB has responded with a 307 Temporary Redirect. The location points to another URI that you can post the event to. This new URI is idempotent for posting, even without an event ID.

curl -i -d "@event.json" "http://127.0.0.1:2113/streams/newstream/incoming/8a00e469-3a99-4517-a0b0-8dc662ffad9b" \
    -H "Content-Type: application/json" -H "ES-EventType: SomeEvent"
1
2
HTTP/1.1 201 Created
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 24 Jul 2018 14:46:10 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11

It's generally recommended to include an event ID if possible as it results in fewer round trips between the client and the server.

When posting to either the stream or to the returned redirect, clients must include the EventType header. If you forget to include the header you receive an error.

curl -i -d "@event.json" "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json"
1
HTTP/1.1 400 Must include an event type with the request either in body or as ES-EventType header.
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type:
Server: Mono-HTTPAPI/1.0
Date: Tue, 24 Jul 2018 14:50:59 GMT
Content-Length: 0
Connection: close
1
2
3
4
5
6
7
8
9
10

Batch append operation

You can append more than one event in a single post by placing multiple events inside the array representing the events, including metadata.

For example, the below has two events:

[
    {
        "eventId": "fbf4b1a1-b4a3-4dfe-a01f-ec52c34e16e4",
        "eventType": "event-type",
        "data": {
            "a": "1"
        }
    },
    {
        "eventId": "0f9fad5b-d9cb-469f-a165-70867728951e",
        "eventType": "event-type",
        "data": {
            "b": "2"
        }
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

When you append multiple events in a single post, EventStoreDB treats them as one transaction, it appends all events together or fails.

curl -i -d "@multiple-events.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json"
1
2
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/1
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:32:18 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10

Appending events

To append events, issue a POST request to the same resource with a new eventId:

[
    {
        "eventId": "fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e5",
        "eventType": "event-type",
        "data": {
            "b": "2"
        }
    }
]
1
2
3
4
5
6
7
8
9
curl -i -d "@event-append.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-EventType: SomeEvent"
1
2
3
curl -i -d "@event-append.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-EventType: SomeEvent"
1
2
3

Data-only events

Version 3.7.0 of EventStoreDB added support for the application/octet-stream content type to support data-only binary events. When creating these events, you need to provide the ES-EventType and ES-EventId headers and cannot have metadata associated with the event. In the example below SGVsbG8gV29ybGQ= is the data you POST to the stream:

curl -i -d "SGVsbG8gV29ybGQ=" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/octet-stream" \
    -H "ES-EventType:rawDataType" \
    -H "ES-EventId:eeccf3ce-4f54-409d-8870-b35dd836cca6"
1
2
3
4
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 27 Jun 2016 13:15:27 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11

Expected version header

The expected version header represents the version of the stream you expect.

For example if you append to a stream at version 1, then you expect it to be at version 1 next time you append. This can allow for optimistic locking when multiple applications are reading/appending to streams.

If your expected version is not the current version you receive an HTTP status code of 400.

WARNING

See the idempotence section below, if you post the same event twice it is idempotent and won't return a version error.

First append an event to a stream, setting a version:

[
    {
        "eventId": "fbf4b1a1-b4a3-4dfe-a01f-ec52c34e16e4",
        "eventType": "event-type",
        "data": {
            "a": "1"
        }
    }
]
1
2
3
4
5
6
7
8
9
curl -i -d @event-version.json "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-CurrentVersion: 0"
1
2
3
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Location: http://127.0.0.1:2113/streams/newstream/2
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 14 Aug 2018 10:02:08 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11

If you now append to the stream with the incorrect version, you receive an HTTP status code 400 error.

curl -i -d @event-version.json "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-ExpectedVersion: 3"
1
2
3
HTTP/1.1 400 Wrong expected EventNumber
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
ES-CurrentVersion: 0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 14 Aug 2018 14:08:44 GMT
Content-Length: 0
Connection: close
1
2
3
4
5
6
7
8
9
10
11

There are special values you can use in the expected version header:

  • -2 states that this append operation should never conflict and should always succeed.
  • -1 states that the stream should not exist at the time of the appending (this append operation creates it).
  • 0 states that the stream should exist but should be empty.

Idempotence

Appends to streams are idempotent based upon the EventId assigned in your post. If you were to re-run the last command it returns the same value again.

This is important behaviour as it's how you implement error handling. If you receive a timeout, broken connection, no answer, etc from your HTTP POST then it's your responsibility to retry the post. You must also keep the same UUID that you assigned to the event in the first POST.

If you are using the expected version parameter with your post, then EventStoreDB is 100% idempotent. If you use -2 as your expected version value, EventStoreDB does its best to keep events idempotent but cannot assure that everything is fully idempotent and you end up in 'at-least-once' messaging. Read this guide for more details on idempotence.

Reading streams and events

Reading a stream

EventStoreDB exposes streams as a resource located at http(s)://{yourdomain.com}:{port}/streams/{stream}. If you issue a simple GET request to this resource, you receive a standard AtomFeed document as a response.

curl -i -H "Accept:application/vnd.eventstore.atom+json" "http://127.0.0.1:2113/streams/newstream" -u "admin:changeit"
1
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "0;-2060438500"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 15 Dec 2017 12:23:23 GMT
Content-Length: 1262
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'newstream'",
  "id": "http://127.0.0.1:2113/streams/newstream",
  "updated": "2017-12-15T12:19:32.021776Z",
  "streamId": "newstream",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "http://127.0.0.1:2113/streams/newstream",
  "eTag": "0;-2060438500",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/newstream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/1/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "0@newstream",
      "id": "http://127.0.0.1:2113/streams/newstream/0",
      "updated": "2017-12-15T12:19:32.021776Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

Reading an event from a stream

The feed has one item in it, and if there are more than one, then items are sorted from newest to oldest.

For each entry, there are a series of links to the actual events, we cover embedding data into a stream later. To GET an event, follow the alternate link and set your Accept headers to the mime type you would like the event in.

The accepted content types for GET requests are:

  • application/xml
  • application/atom+xml
  • application/json
  • application/vnd.eventstore.atom+json
  • text/xml
  • text/html

The non-atom version of the event has fewer details about the event.

curl -i http://127.0.0.1:2113/streams/newstream/0 -H "Accept: application/json" -u "admin:changeit"
1
curl -i http://127.0.0.1:2113/streams/newstream/0 -H "Accept: application/json" -u "admin:changeit"
1

Feed paging

The next step in understanding how to read a stream is the first/last/previous/next links within a stream. EventStoreDB supplies these links, so you can read through a stream, and they follow the pattern defined in RFC 5005open in new window.

In the example above the server returned the following links as part of its result:

curl -i -H "Accept:application/vnd.eventstore.atom+json" "http://127.0.0.1:2113/streams/newstream" -u "admin:changeit"
1
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "0;-2060438500"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 15 Dec 2017 12:23:23 GMT
Content-Length: 1262
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'newstream'",
  "id": "http://127.0.0.1:2113/streams/newstream",
  "updated": "2017-12-15T12:19:32.021776Z",
  "streamId": "newstream",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "http://127.0.0.1:2113/streams/newstream",
  "eTag": "0;-2060438500",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/newstream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/1/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "0@newstream",
      "id": "http://127.0.0.1:2113/streams/newstream/0",
      "updated": "2017-12-15T12:19:32.021776Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

This shows that there is not a next URL as all the information is in this request and that the URL requested is the first link. When dealing with these URLs, there are two ways of reading the data in the stream.

  • You GET the last link and move backwards following previous links, or
  • You GET the first link and follow the next links, and the final item will not have a next link.

If you want to follow a live stream, then you keep following the previous links. When you reach the end of a stream, you receive an empty document with no entries or previous link. You then continue polling this URI (in the future a document will appear). You can see this by trying the previous link from the above feed.

curl -i http://127.0.0.1:2113/streams/newstream/1/forward/20 -H "Accept:application/vnd.eventstore.atom+json"
1
HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "0;248368668"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 13 Mar 2015 14:04:47 GMT
Content-Length: 795
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'newstream'",
  "id": "<http://127.0.0.1:2113/streams/newstream">,
  "updated": "0001-01-01T00:00:00Z",
  "streamId": "newstream",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": false,
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/newstream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/0/forward/20",
      "relation": "last"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/0/backward/20",
      "relation": "next"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": \[]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

When parsing an atom subscription, the IDs of events always stay the same. This is important for figuring out if you are referring to the same event.

Paging through events

Let's now try an example with more than a single page. First create the multiple events:

curl -i -d "@paging-events.json" "http://127.0.0.1:2113/streams/alphabet" -H "Content-Type:application/vnd.eventstore.events+json"
1
HTTP/1.1 100 Continue

HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Location: http://127.0.0.1:2113/streams/alphabet/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 21 Aug 2018 09:53:46 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11
12
13

If you request the stream of events, you see a series of links above the events:

curl -i http://127.0.0.1:2113/streams/alphabet \
    -H "Accept:application/vnd.eventstore.atom+json"
1
2
#region responseHeader
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "26;-2060438500"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 21 Aug 2018 10:12:31 GMT
Content-Length: 10727
Keep-Alive: timeout=15,max=100
#endregion responseHeader

{
  "title": "Event stream 'alphabet'",
  "id": "http://127.0.0.1:2113/streams/alphabet",
  "updated": "2018-08-21T09:53:46.869815Z",
  "streamId": "alphabet",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "http://127.0.0.1:2113/streams/alphabet",
  "eTag": "26;-2060438500",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/0/forward/20",
      "relation": "last"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/6/backward/20",
      "relation": "next"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/27/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "26@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/26",
      "updated": "2018-08-21T09:53:46.869815Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/26",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/26",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "25@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/25",
      "updated": "2018-08-21T09:53:46.869811Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/25",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/25",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "24@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/24",
      "updated": "2018-08-21T09:53:46.869809Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/24",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/24",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "23@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/23",
      "updated": "2018-08-21T09:53:46.869806Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/23",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/23",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "22@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/22",
      "updated": "2018-08-21T09:53:46.869804Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/22",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/22",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "21@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/21",
      "updated": "2018-08-21T09:53:46.869802Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/21",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/21",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "20@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/20",
      "updated": "2018-08-21T09:53:46.869799Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/20",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/20",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "19@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/19",
      "updated": "2018-08-21T09:53:46.869791Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/19",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/19",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "18@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/18",
      "updated": "2018-08-21T09:53:46.869788Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/18",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/18",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "17@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/17",
      "updated": "2018-08-21T09:53:46.869786Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/17",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/17",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "16@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/16",
      "updated": "2018-08-21T09:53:46.869782Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/16",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/16",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "15@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/15",
      "updated": "2018-08-21T09:53:46.86978Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/15",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/15",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "14@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/14",
      "updated": "2018-08-21T09:53:46.869778Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/14",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/14",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "13@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/13",
      "updated": "2018-08-21T09:53:46.869773Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/13",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/13",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "12@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/12",
      "updated": "2018-08-21T09:53:46.869771Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/12",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/12",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "11@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/11",
      "updated": "2018-08-21T09:53:46.869769Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/11",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/11",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "10@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/10",
      "updated": "2018-08-21T09:53:46.869766Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/10",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/10",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "9@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/9",
      "updated": "2018-08-21T09:53:46.869764Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/9",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/9",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "8@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/8",
      "updated": "2018-08-21T09:53:46.86976Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/8",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/8",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "7@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/7",
      "updated": "2018-08-21T09:53:46.869758Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/7",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/7",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436

Using the links in the stream of events, you can traverse through all the events in the stream by going to the last URL and following previous links, or by following next links from the first link.

For example, if you request the last link from above:

curl -i http://127.0.0.1:2113/streams/alphabet/0/forward/20 \
    -H "Accept:application/vnd.eventstore.atom+json"
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=31536000, public
Vary: Accept
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 21 Aug 2018 10:24:28 GMT
Content-Length: 10403
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'alphabet'",
  "id": "http://127.0.0.1:2113/streams/alphabet",
  "updated": "2018-08-21T09:53:46.869716Z",
  "streamId": "alphabet",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": false,
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/20/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/alphabet/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "19@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/19",
      "updated": "2018-08-21T09:53:46.869791Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/19",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/19",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "18@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/18",
      "updated": "2018-08-21T09:53:46.869788Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/18",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/18",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "17@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/17",
      "updated": "2018-08-21T09:53:46.869786Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/17",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/17",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "16@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/16",
      "updated": "2018-08-21T09:53:46.869782Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/16",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/16",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "15@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/15",
      "updated": "2018-08-21T09:53:46.86978Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/15",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/15",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "14@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/14",
      "updated": "2018-08-21T09:53:46.869778Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/14",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/14",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "13@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/13",
      "updated": "2018-08-21T09:53:46.869773Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/13",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/13",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "12@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/12",
      "updated": "2018-08-21T09:53:46.869771Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/12",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/12",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "11@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/11",
      "updated": "2018-08-21T09:53:46.869769Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/11",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/11",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "10@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/10",
      "updated": "2018-08-21T09:53:46.869766Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/10",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/10",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "9@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/9",
      "updated": "2018-08-21T09:53:46.869764Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/9",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/9",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "8@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/8",
      "updated": "2018-08-21T09:53:46.86976Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/8",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/8",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "7@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/7",
      "updated": "2018-08-21T09:53:46.869758Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/7",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/7",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "6@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/6",
      "updated": "2018-08-21T09:53:46.869755Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/6",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/6",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "5@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/5",
      "updated": "2018-08-21T09:53:46.869753Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/5",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/5",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "4@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/4",
      "updated": "2018-08-21T09:53:46.86975Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/4",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/4",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "3@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/3",
      "updated": "2018-08-21T09:53:46.869748Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/3",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/3",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "2@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/2",
      "updated": "2018-08-21T09:53:46.869746Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/2",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/2",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "1@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/1",
      "updated": "2018-08-21T09:53:46.869742Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/1",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/1",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "0@alphabet",
      "id": "http://127.0.0.1:2113/streams/alphabet/0",
      "updated": "2018-08-21T09:53:46.869716Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/alphabet/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423

You then follow previous links until you are back to the head of the stream, where you can continue reading events in real time by polling the previous link.

TIP

All links except the head link are fully cacheable as you can see in the HTTP header Cache-Control: max-age=31536000, public. This is important when discussing intermediaries and performance as you commonly replay a stream from storage. You should never bookmark links aside from the head of the stream resource, and always follow links. We may in the future change how internal links work, and bookmarking links other than the head may break.

Reading all events

$all is a special paged stream for all events. You can use the same paged form of reading described above to read all events for a node by pointing the stream at /streams/$all. As it's a stream like any other, you can perform all operations, except posting to it.

TIP

To access the $all stream, you must use admin details. Find more information on the security page.

curl -i http://127.0.0.1:2113/streams/%24all \
    -H "Accept:application/vnd.eventstore.atom+json" -u admin:changeit
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "25159393;248368668"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 13 Mar 2015 16:19:09 GMT
Content-Length: 12157
Keep-Alive: timeout=15,max=100

{
  "title": "All events",
  "id": "<http://127.0.0.1:2113/streams/%24all">,
  "updated": "2015-03-13T16:19:06.548415Z",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": false,
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/%24all",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24all/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24all/00000000000000000000000000000000/forward/20",
      "relation": "last"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24all/00000000017BC0D000000000017BC0D0/backward/20",
      "relation": "next"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24all/0000000001801EBF0000000001801EBF/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24all/metadata",
      "relation": "metadata"
    }
  ],
  "entries": []
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

Conditional GETs

The head link supports conditional GETs with the use of ETAGSopen in new window, a well-known HTTP construct. You can include the ETAG of your last request and issue a conditional GET to the server. If nothing has changed, it won't return the full feed. For example the earlier response has an ETAG:

HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "26;-2060438500"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 21 Aug 2018 10:12:31 GMT
Content-Length: 10727
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11
12
13

You can use this in your next request when polling the stream for changes by putting it in the If-None-Match header. This tells the server to check if the response is the one you already know and returning a '304 not modified' response. If the tags have changed, the server returns a '200 OK' response. You can use this method to optimise your application by not sending large streams if there are no changes.

curl -i http://127.0.0.1:2113/streams/alphabet \
    -H "Accept:application/vnd.eventstore.atom+json" \
    -H "If-None-Match:26;-2060438500"
1
2
3
HTTP/1.1 304 Not Modified
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Tue, 21 Aug 2018 12:07:35 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10

TIP

You create Etags using the version of the stream and the media type of the stream you are reading. You can't read an Etag from a stream in one media type and use it with another media type.

Embedding data into streams in JSON format

So far in this guide, the feeds returned have contained links that refer to the actual event data. This is normally a preferable mechanism for several reasons:

  • They can be in a different media type than the feed, and you can negotiate them separately from the feed itself (for example, the feed in JSON, the event in XML). You can cache the event data separately from the feed, and you can point it to different feeds. If you use a linkTo() in your projection this is what happens in your atom feeds.
  • If you are using JSON, you can embed the events into the atom feed events. This can help cut down on the number of requests in some situations, but the messages are larger.

There are ways of embedding events and further metadata into your stream by using the embed parameter.

Rich embed mode

The rich embed mode returns more properties about the event (eventtype, streamid, position, and so on) as you can see in the following request.

curl -i -H "Accept:application/vnd.eventstore.atom+json" \
    "http://127.0.0.1:2113/streams/newstream?embed=rich"
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "0;248368668"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 13 Mar 2015 16:30:57 GMT
Content-Length: 1570
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'newstream'",
  "id": "<http://127.0.0.1:2113/streams/newstream">,
  "updated": "2015-03-13T12:13:42.492473Z",
  "streamId": "newstream",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "<http://127.0.0.1:2113/streams/newstream">,
  "eTag": "0;248368668",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/newstream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/1/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "eventId": "fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e4",
      "eventType": "event-type",
      "eventNumber": 0,
      "streamId": "newstream",
      "isJson": true,
      "isMetaData": false,
      "isLinkMetaData": false,
      "positionEventNumber": 0,
      "positionStreamId": "newstream",
      "title": "0@newstream",
      "id": "<http://127.0.0.1:2113/streams/newstream/0">,
      "updated": "2015-03-13T12:13:42.492473Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

Body embed mode

The body embed mode returns the JSON/XML body of the events into the feed as well, depending on the type of the feed. You can see this in the request below:

curl -i -H "Accept:application/vnd.eventstore.atom+json" \
    "http://127.0.0.1:2113/streams/newstream?embed=body"
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "0;248368668"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 13 Mar 2015 16:32:06 GMT
Content-Length: 1608
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream 'newstream'",
  "id": "<http://127.0.0.1:2113/streams/newstream">,
  "updated": "2015-03-13T12:13:42.492473Z",
  "streamId": "newstream",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "<http://127.0.0.1:2113/streams/newstream">,
  "eTag": "0;248368668",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/newstream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/1/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/newstream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": \[
    {
      "eventId": "fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e4",
      "eventType": "event-type",
      "eventNumber": 0,
      "data": "{\\n  "a": "1"\\n}",
      "streamId": "newstream",
      "isJson": true,
      "isMetaData": false,
      "isLinkMetaData": false,
      "positionEventNumber": 0,
      "positionStreamId": "newstream",
      "title": "0@newstream",
      "id": "<http://127.0.0.1:2113/streams/newstream/0">,
      "updated": "2015-03-13T12:13:42.492473Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "event-type",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/newstream/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Variants of body embed mode

Two other modes are variants of body:

  • PrettyBody tries to reformat the JSON to make it "pretty to read".
  • TryHarder works harder to try to parse and reformat the JSON from an event to return it in the feed. These do not include further information and are focused on how the feed looks.

Deleting a stream

Soft deleting

To delete a stream over the Atom interface, issue a DELETE request to the resource.

curl -X DELETE "http://127.0.0.1:2113/streams/newstream"
1
HTTP/1.1 204 Stream deleted
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 27 Jul 2018 11:51:02 GMT
Content-Length: 0
Connection: close
1
2
3
4
5
6
7
8
9
10

By default when you delete a stream, EventStoreDB soft deletes it. This means you can recreate it later by setting the $tb metadata section in the stream. If you try to GET a soft deleted stream you receive a 404 response:

curl -X GET "http://127.0.0.1:2113/streams/newstream" \
    -H 'Accept: application/vnd.eventstore.events+json'
1
2
HTTP/1.1 410 Deleted
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 27 Jul 2018 12:04:10 GMT
Content-Length: 0
Connection: close
1
2
3
4
5
6
7
8
9
10

You can recreate the stream by appending new events to it (like creating a new stream):

curl -i -d "@event-append.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-EventType: SomeEvent"
1
2
3
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/1
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:32:18 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10

The version numbers do not start at zero but at where you soft deleted the stream from

Hard deleting

You can hard delete a stream. To issue a permanent delete use the ES-HardDelete header.

WARNING

A hard delete is permanent and the stream is not removed during a scavenge. If you hard delete a stream, you cannot recreate the stream.

Issue the DELETE as before but with the permanent delete header:

curl -X DELETE http://127.0.0.1:2113/streams/newstream -H "ES-HardDelete:true"
1
HTTP/1.1 204 Stream deleted
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location
Date: Thu, 13 Mar 2014 20:56:55 GMT
1
2
3
4
5
6
7
8
9

The stream is now permanently deleted, and now the response is a 410.

curl -X GET "http://127.0.0.1:2113/streams/newstream" \
    -H 'Accept: application/vnd.eventstore.events+json'
1
2
HTTP/1.1 410 Deleted
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 27 Jul 2018 12:04:10 GMT
Content-Length: 0
Connection: close
1
2
3
4
5
6
7
8
9
10

If you try to recreate the stream as in the above example you also receive a 410 response.

curl -i -d "@event-append.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-EventType: SomeEvent"
1
2
3
curl -i -d "@event-append.json" "http://127.0.0.1:2113/streams/newstream" \
    -H "Content-Type:application/vnd.eventstore.events+json" \
    -H "ES-EventType: SomeEvent"
1
2
3

Description document

With the addition of Competing Consumers, which is another way of reading streams, the need arose to expose these different methods to consumers.

The introduction of the description document has some benefits:

  • Clients can rely on the keys (streams, streamSubscription) in the description document to remain unchanged across versions of EventStoreDB and you can rely on it as a lookup for the particular method of reading a stream.
  • Allows the restructuring of URIs underneath without breaking clients. e.g., /streams/newstream -> /streams/newstream/atom.

Fetching the description document

There are three ways in which EventStoreDB returns the description document.

  • Attempting to read a stream with an unsupported media type.
  • Attempting to read a stream with no accept header.
  • Requesting the description document explicitly.

The client is able to request the description document by passing application/vnd.eventstore.streamdesc+json in the accept header, for example:

curl -i http://localhost:2113/streams/newstream \
    -H "accept:application/vnd.eventstore.streamdesc+json"
1
2
HTTP/1.1 200 Description Document
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Content-Type: application/vnd.eventstore.streamdesc+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Thu, 23 Aug 2018 12:37:18 GMT
Content-Length: 517
Keep-Alive: timeout=15,max=100

{
  "title": "Description document for 'newstream'",
  "description": "The description document will be presented when no accept header is present or it was requested",
  "_links": {
    "self": {
      "href": "/streams/newstream",
      "supportedContentTypes": [
        "application/vnd.eventstore.streamdesc+json"
      ]
    },
    "stream": {
      "href": "/streams/newstream",
      "supportedContentTypes": [
        "application/atom+xml",
        "application/vnd.eventstore.atom+json"
      ]
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

In the example above, the client requested the description document for the stream called newstream which has a set of links describing the supported methods and content types. The document also includes additional methods available such as the streamSubscription. If there are no subscriptions to the newstream, the streamSubscription key is absent.

Optimistic concurrency and idempotence

Idempotence

All operations on the HTTP interface are idempotent (unless the expected version is ignored). It is the responsibility of the client to retry operations under failure conditions, ensuring that the event IDs of the events posted are the same as the first attempt.

Provided the client maintains this EventStoreDB will treat all operations as idempotent.

For example:

curl -i -d @event.txt "http://127.0.0.1:2113/streams/newstream"
1
HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, PUT, DELETE
Location: http://127.0.0.1:2113/streams/newstream444/1
Content-Type: application/json
Server: Mono-HTTPAPI/1.0
Date: Thu, 06 Sep 2012 19:49:37 GMT
Content-Length: 107
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9

Assuming you were posting to a new stream you would get the event appended once (and the stream created). The second event returns as the first but not write again.

TIP

This allows the client rule of “if you get an unknown condition, retry” to work.

For example:

curl -i "http://127.0.0.1:2113/streams/newstream444"
1
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, PUT, DELETE
Content-Type: application/json
Server: Mono-HTTPAPI/1.0
Date: Thu, 06 Sep 2012 19:50:30 GMT
Content-Length: 2131
Keep-Alive: timeout=15,max=100

{
	"title": "Event stream 'newstream444'",
	"id": "http://127.0.0.1:2113/streams/newstream444",
	"updated": "2012-09-06T16:39:44.695643Z",
	"author": {
		"name": "EventStore"
	},
	"links": [
		{
			"uri": "http://127.0.0.1:2113/streams/newstream444",
			"relation": "self"
		},
		{
			"uri": "http://127.0.0.1:2113/streams/newstream444",
			"relation": "first"
		}
	],
	"entries": [
		{
			"title": "newstream444 #1",
			"id": "http://127.0.0.1:2113/streams/newstream444/1",
			"updated": "2012-09-06T16:39:44.695643Z",
			"author": {
				"name": "EventStore"
			},
			"summary": "Entry #1",
			"links": [
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/1",
					"relation": "edit"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/1?format=text",
					"type": "text/plain"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/1?format=json",
					"relation": "alternate",
					"type": "application/json"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/1?format=xml",
					"relation": "alternate",
					"type": "text/xml"
				}
			]
		},
		{
			"title": "newstream444 #0",
			"id": "http://127.0.0.1:2113/streams/newstream444/0",
			"updated": "2012-09-06T16:39:44.695631Z",
			"author": {
				"name": "EventStore"
			},
			"summary": "Entry #0",
			"links": [
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/0",
					"relation": "edit"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/0?format=text",
					"type": "text/plain"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/0?format=json",
					"relation": "alternate",
					"type": "application/json"
				},
				{
					"uri": "http://127.0.0.1:2113/streams/newstream444/event/0?format=xml",
					"relation": "alternate",
					"type": "text/xml"
				}
			]
		}
	]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

Stream metadata

Every stream in EventStoreDB has metadata stream associated with it, prefixed by $, so the metadata stream from a stream called foo is $foo. Internally, the metadata includes information such as the ACL of the stream, the maximum count and age for the events in the stream. Client code can also add information into stream metadata for use with projections or the client API.

Stream metadata is stored internally as JSON, and you can access it over the HTTP API.

Reading stream metadata

To read the metadata, issue a GET request to the attached metadata resource, which is typically of the form:

http://{eventstore-ip-address}/streams/{stream-name}/metadata
1

You should not access metadata by constructing this URL yourself, as the right to change the resource address is reserved. Instead, you should follow the link from the stream itself, which enables your client to tolerate future changes to the addressing structure.

curl -i -H "Accept:application/vnd.eventstore.atom+json" \
    http://127.0.0.1:2113/streams/%24users --user admin:changeit
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
ETag: "3;-2060438500"
Content-Type: application/vnd.eventstore.atom+json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Thu, 23 Aug 2018 10:03:34 GMT
Content-Length: 2670
Keep-Alive: timeout=15,max=100

{
  "title": "Event stream '$users'",
  "id": "http://127.0.0.1:2113/streams/%24users",
  "updated": "2018-08-23T09:19:37.880827Z",
  "streamId": "$users",
  "author": {
    "name": "EventStore"
  },
  "headOfStream": true,
  "selfUrl": "http://127.0.0.1:2113/streams/%24users",
  "eTag": "3;-2060438500",
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/%24users",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24users/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24users/4/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/%24users/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "3@$users",
      "id": "http://127.0.0.1:2113/streams/%24users/3",
      "updated": "2018-08-23T09:19:37.880827Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "$User",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/3",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/3",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "2@$users",
      "id": "http://127.0.0.1:2113/streams/%24users/2",
      "updated": "2018-08-23T09:08:40.499762Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "$User",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/2",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/2",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "1@$users",
      "id": "http://127.0.0.1:2113/streams/%24users/1",
      "updated": "2018-08-23T07:55:39.833203Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "$User",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/1",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/1",
          "relation": "alternate"
        }
      ]
    },
    {
      "title": "0@$users",
      "id": "http://127.0.0.1:2113/streams/%24users/0",
      "updated": "2018-08-23T07:55:39.829589Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "$User",
      "links": [
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:2113/streams/%24users/0",
          "relation": "alternate"
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

Once you have the URI of the metadata stream, issue a GET request to retrieve the metadata:

curl -i -H "Accept:application/vnd.eventstore.atom+json" http://127.0.0.1:2113/streams/%24users/metadata --user admin:changeit
1

If you have security enabled, reading metadata may require that you pass credentials, as in the examples above. If credentials are required and you do not pass them, then you receive a 401 Unauthorized response.

curl -i -H "Accept:application/vnd.eventstore.atom+json" http://127.0.0.1:2113/streams/%24users
1
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Methods: GET, POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
WWW-Authenticate: Basic realm="ES"
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Thu, 23 Aug 2018 10:26:52 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11

Writing metadata

To update the metadata for a stream, issue a POST request to the metadata resource.

Inside a file named metadata.json:

[
    {
        "eventId": "7c314750-05e1-439f-b2eb-f5b0e019be72",
        "eventType": "$user-updated",
        "data": {
            "readRole": "$all",
            "metaReadRole": "$all"
        }
    }
]
1
2
3
4
5
6
7
8
9
10

You can also add user-specified metadata here. Some examples user-specified metadata are:

  • Which adapter populates a stream.
  • Which projection created a stream.
  • A correlation ID to a business process.

You then post this information is then posted to the stream:

curl -i -d @metadata.json http://127.0.0.1:2113/streams/%24users/metadata \
    --user admin:changeit \
    -H "Content-Type: application/vnd.eventstore.events+json"
1
2
3
HTTP/1.1 201 Created
Access-Control-Allow-Methods: GET, POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-Forwarded-Prefix, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTos
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position, ES-CurrentVersion
Location: http://127.0.0.1:2113/streams/%24%24%24users/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Thu, 23 Aug 2018 10:35:19 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100
1
2
3
4
5
6
7
8
9
10
11

If the specified user does not have permissions to write to the stream metadata, you receive a '401 Unauthorized' response.