Error reference

invalid_cursor

HTTP 400 retryable: false REST and MCP

The cursor you sent is not one this endpoint can resume from, either because it does not decode or because it was minted against a different set of filters.

Why it happens

A mail cursor is not an opaque offset. It carries a version, a binding that identifies the query it was created for, a sort point and an expiry, and all four are checked on the way back in. Changing the filter between pages therefore invalidates the cursor rather than silently paginating a different result set. The event cursor is a separate, simpler thing: a decimal sequence number that must be a positive integer inside the signed 64-bit range.

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 the listing without a cursor. The first page is always reachable, and the cursor you were holding cannot be repaired.
  2. Keep the query parameters identical across a pagination run. A cursor is bound to the filter it was issued under, so changing the search term or the inbox mid-run invalidates it.
  3. Pass the cursor back exactly as received. Do not URL-decode it twice, trim it, or store it somewhere that truncates.
  4. For event polling, persist the cursor as a string. Some JSON parsers round a 19-digit sequence number through a double and corrupt the low digits.

Why it works this way

Binding the cursor to the filter is the reason this error exists at all. The alternative is to accept the cursor and paginate the new filter from an offset that means nothing in it, which returns a plausible page that silently skips or repeats records.

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 400
content-type: application/json
x-request-id: req_...

{
  "error": {
    "code": "invalid_cursor",
    "message": "Cursor is invalid, expired, or belongs to another filter.",
    "request_id": "req_...",
    "retryable": false
  }
}

Verified against packages/domain/src/mail-pages.ts:34. 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