Error reference

cursor_expired

HTTP 410 retryable: false REST and MCP

Your event cursor is well-formed but the events it would resume from have already been trimmed, so there is no correct page to return.

Why it happens

Before serving an event page the endpoint reads the minimum project_sequence still present for the project and compares it with the cursor. If the next sequence you asked for is lower than the oldest surviving event, the gap between your cursor and the retained window is real, and returning the oldest remaining events would look like a normal page while quietly omitting everything in between. A 410 is used rather than a 404 because the resource existed and is gone, which is exactly what 410 is for.

Every condition below was read in the implementation at the file and line given. They are the branches that raise this code, not a summary of what the name suggests.

How to fix it

  1. Restart polling with no cursor. You will receive the first remaining event and can resume normally from there.
  2. Treat the gap as real. Events between your old cursor and the new start were not delivered and will not be, so reconcile current state from the REST resources rather than assuming the stream was continuous.
  3. Poll more often, or subscribe a webhook instead. A signed webhook removes the polling window entirely, and the events surface stays available as a backstop.
  4. Do not retry with the same cursor. The response is marked retryable: false and the condition cannot resolve on its own.

Why it works this way

This is the deliberate opposite of a silent restart. A consumer that lost events needs to know it lost them, because the whole value of the event stream is the guarantee that consecutive sequence numbers mean nothing happened in between.

Messages you will see

The message field is written for a human reading a log and is not a contract value. Branch on code instead. These are the exact strings this code is sent with today, listed so that a search for one of them lands here.

Where it appears

These are the documented surfaces that raise it. The REST base is https://api.emailforagents.ai/v1, and the remote MCP endpoint is https://mcp.emailforagents.ai/mcp.

The response

Read request_id off the body or the x-request-id header before doing anything else. It is the only handle on one specific attempt, and for a 503 it is the only handle at all, because the underlying detail stays server-side.

HTTP/1.1 410
content-type: application/json
x-request-id: req_...

{
  "error": {
    "code": "cursor_expired",
    "message": "This event cursor is expired. Restart polling from the first remaining event without a cursor.",
    "request_id": "req_...",
    "retryable": false
  }
}

Verified against packages/domain/src/services.ts:3151. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one place a code is mapped to a status.

All error codes · Documentation · Quickstart