Optional HTTP headers

EventStoreDB supports custom HTTP headers for requests. The headers were previously in the form X-ES-ExpectedVersion but were changed to ES-ExpectedVersion in compliance with RFC-6648open in new window.

The headers supported are:

HeaderDescription
ES-ExpectedVersionThe expected version of the stream (allows optimistic concurrency)
ES-ResolveLinkToWhether to resolve linkTos in stream
ES-RequiresMasterWhether this operation needs to run on the master node
ES-TrustedAuthAllows a trusted intermediary to handle authentication
ES-LongPollInstructs the server to do a long poll operation on a stream read
ES-HardDeleteInstructs the server to hard delete the stream when deleting as opposed to the default soft delete
ES-EventTypeInstructs the server the event type associated to a posted body
ES-EventIdInstructs the server the event id associated to a posted body

EventID

When you append to a stream and don't use the application/vnd.eventstore.events+json/+xml media type, you need to specify an event ID with the event you post. This is not required with the custom media type as it is specified within the format (there is an EventId on each entry in the format). EventStoreDB uses EventId for impotency.

You can include an event ID on an event by specifying this header.

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

If you don't add an ES-EventId header on an append where the body is considered the actual event (e.g., not using application/vnd.eventstore.events+json/+xml) EventStoreDB generates a unique identifier for you and redirects you to an idempotent URI where you can post your event. If you can create a UUID then you shouldn't use this feature, but it's useful when you cannot create a UUID.

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

EventStoreDB returned a 307 Temporary Redirect with a location header that points to a generated URI that is idempotent for purposes of retrying the post.

EventType

When you append to a stream and don't the application/vnd.eventstore.events+json/+xml media type you must specify an event type with the event that you are posting. This isn't required with the custom media type as it's specified within the format itself.

You use the ES-EventType header as follows.

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

If you view the event in the UI or with cURL it has the EventType of SomeEvent:

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

Expected Version

When you append to a stream you often want to use Expected Version to allow for optimistic concurrency with a stream. You commonly use this for a domain object projection.

i.e., "A append operations can succeed if I have seen everyone else's append operations."

You set ExpectedVersion with the syntax ES-ExpectedVersion: #, where # is an integer version number. There are other special values available:

  • 0, the stream should exist but be empty when appending.
  • -1, the stream should not exist when appending.
  • -2, the write should not conflict with anything and should always succeed.
  • -4, the stream or a metadata stream should exist when appending.

If the ExpectedVersion does not match the version of the stream, EventStoreDB returns an HTTP 400 Wrong expected EventNumber response. This response contains the current version of the stream in an ES-CurrentVersion header.

In the following cURL command ExpectedVersion is not set, and it appends or create/append to the stream.

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

The stream newstream has one event. If you append with an expected version of 3, you receive an 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

You can see from the ES-CurrentVersion header above that the stream is at version 0. Appending with an expected version of 0 works. The expected version is always the version of the last event known in the stream.

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

HardDelete

The ES-HardDelete header controls deleting a stream. By default EventStoreDB soft deletes a stream allowing you to later reuse that stream. If you set the ES-HardDelete header EventStoreDB permanently deletes the stream.

curl -X DELETE http://127.0.0.1:2113/streams/newstream -H "ES-HardDelete:true"
1
curl -X DELETE http://127.0.0.1:2113/streams/newstream -H "ES-HardDelete:true"
1

This changes the general behaviour from returning a 404 and the stream to be recreated (soft-delete) to the stream now return a 410 Deleted response.

LongPoll

You use the ES-LongPoll header to tell EventStoreDB that when on the head link of a stream and no data is available to wait a period of time to see if data becomes available.

You can use this to give lower latency for Atom clients instead of client initiated polling.

Instead of the client polling every 5 seconds to get data from the feed the client sends a request with ES-LongPoll: 15. This instructs EventStoreDB to wait for up to 15 seconds before returning with no result. The latency is therefore lowered from the poll interval to about 10ms from the time an event is appended until the time the HTTP connection is notified.

You can see the use of the ES-LongPoll header in the following cURL command.

First go to the head of the stream.

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

Then fetch the previous rel link http://127.0.0.1:2113/streams/newstream/2/forward/20 and try it. It returns an empty feed.

curl -i http://127.0.0.1:2113/streams/newstream/2/forward/20 \
    -H "Accept: application/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=0, no-cache, must-revalidate
Vary: Accept
ETag: "1;1391431453"
Content-Type: application/json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 27 Aug 2018 09:53:14 GMT
Content-Length: 786
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": true,
  "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/1/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

The entries section is empty (there is no further data to provide). Now try the same URI with a long poll header.

curl -i http://127.0.0.1:2113/streams/newstream/2/forward/20 \
    -H "Accept: application/json" \
    -H "ES-LongPoll: 10"
1
2
3

If you do not insert any events into the stream while this is running it takes 10 seconds for the HTTP request to finish. If you append an event to the stream while its running you see the result for that request when you append the event.

Requires Master

When running in a clustered environment there are times when you only want an operation to happen on the current leader node. A client can fetch information in an eventually consistent fashion by communicating with the servers. The TCP client included with the multi-node version does this.

Over HTTP the RequiresMaster header tells the node that it is not allowed to serve a read or forward a write request. If the node is the leader everything works as normal, if it isn't it responds with a 307 temporary redirect to the leader.

Run the below on the master:

curl -i "http://127.0.0.1:32004/streams/newstream" \
    -H "ES-RequireMaster: True"
1
2
HTTP/1.1 200 OK
Cache-Control: max-age=0, no-cache, must-revalidate
Content-Length: 1296
Content-Type: application/vnd.eventstore.atom+json; charset: utf-8
ETag: "0;-2060438500"
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Date: Thu, 27 Jun 2013 14:48:37 GMT

{
  "title": "Event stream 'stream'",
  "id": "http://127.0.0.1:32004/streams/stream",
  "updated": "2013-06-27T14:48:15.2596358Z",
  "streamId": "stream",
  "author": {
    "name": "EventStore"
  },
  "links": [
    {
      "uri": "http://127.0.0.1:32004/streams/stream",
      "relation": "self"
    },
    {
      "uri": "http://127.0.0.1:32004/streams/stream/head/backward/20",
      "relation": "first"
    },
    {
      "uri": "http://127.0.0.1:32004/streams/stream/0/forward/20",
      "relation": "last"
    },
    {
      "uri": "http://127.0.0.1:32004/streams/stream/1/forward/20",
      "relation": "previous"
    },
    {
      "uri": "http://127.0.0.1:32004/streams/stream/metadata",
      "relation": "metadata"
    }
  ],
  "entries": [
    {
      "title": "0@stream",
      "id": "http://127.0.0.1:32004/streams/stream/0",
      "updated": "2013-06-27T14:48:15.2596358Z",
      "author": {
        "name": "EventStore"
      },
      "summary": "TakeSomeSpaceEvent",
      "links": [
        {
          "uri": "http://127.0.0.1:32004/streams/stream/0",
          "relation": "edit"
        },
        {
          "uri": "http://127.0.0.1:32004/streams/stream/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

Run the following on any other node:

curl -i "http://127.0.0.1:31004/streams/newstream" \
    -H "ES-RequireMaster: True"
1
2
HTTP/1.1 307 Temporary Redirect
Content-Length: 0
Content-Type: text/plain; charset: utf-8
Location: http://127.0.0.1:32004/streams/stream
Server: Microsoft-HTTPAPI/2.0
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Date: Thu, 27 Jun 2013 14:48:28 GMT
1
2
3
4
5
6
7
8
9

Resolve LinkTo

When using projections you can have links placed into another stream. By default EventStoreDB always resolve linkTos for you returning the event that points to the link. You can use the ES-ResolveLinkTos: false HTTP header to tell EventStoreDB to return you the actual link and to not resolve it.

You can see the differences in behaviour in the following cURL commands.

curl -i http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0 \
    -H "accept:application/vnd.eventstore.atom+json"
    -H "ES-ResolveLinkTos: true"
1
2
3
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, 28 Aug 2018 13:13:49 GMT
Content-Length: 918
Keep-Alive: timeout=15,max=100

{
  "title": "0@shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167",
  "id": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0",
  "updated": "2018-08-28T12:56:15.263731Z",
  "author": {
    "name": "EventStore"
  },
  "summary": "ItemAdded",
  "content": {
    "eventStreamId": "shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167",
    "eventNumber": 0,
    "eventType": "ItemAdded",
    "eventId": "b989fe21-9469-4017-8d71-9820b8dd1167",
    "data": {
      "Description": "Xbox One Elite (Console)"
    },
    "metadata": {
      "TimeStamp": "2016-12-23T10:00:00.9225401+01:00"
    }
  },
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0",
      "relation": "edit"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/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

TIP

The content links are pointing to the original projection stream. The linked events are resolved back to where they point. With the header set the links (or embedded content) instead point back to the actual linkTo events.

curl -i http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0 \
    -H "accept:application/vnd.eventstore.atom+json"
    -H "ES-ResolveLinkTos: false"
1
2
3
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, 28 Aug 2018 13:22:09 GMT
Content-Length: 918
Keep-Alive: timeout=15,max=100

{
  "title": "0@shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167",
  "id": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0",
  "updated": "2018-08-28T12:56:15.263731Z",
  "author": {
    "name": "EventStore"
  },
  "summary": "ItemAdded",
  "content": {
    "eventStreamId": "shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167",
    "eventNumber": 0,
    "eventType": "ItemAdded",
    "eventId": "b989fe21-9469-4017-8d71-9820b8dd1167",
    "data": {
      "Description": "Xbox One Elite (Console)"
    },
    "metadata": {
      "TimeStamp": "2016-12-23T10:00:00.9225401+01:00"
    }
  },
  "links": [
    {
      "uri": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/0",
      "relation": "edit"
    },
    {
      "uri": "http://127.0.0.1:2113/streams/shoppingCart-b989fe21-9469-4017-8d71-9820b8dd1167/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