Events
An ordered feed of what happens in the account, for polling or long-polling. The only event
type today is message.received.
List account events (poll or long-poll)
Section titled “List account events (poll or long-poll)”GET /v1/events
Auth: API key (Authorization: Bearer rz_live_…)
Returns the events whose sequence number is greater than after, oldest first. Keep
next_after and pass it as after next time to get only new events. The only event type
today is message.received; ignore types you do not know.
- With
waitabove 0 and no new events, the request is held for up towaitseconds and returns as soon as an event arrives. Held requests share the limit of 5 per account withGET /v1/inboxes/{id}/wait(429 too_many_waits). statusistimeoutwhenever no events are returned (also withwait=0);next_afterthen equalsafter.- Sequence numbers increase but can have gaps: events of deleted inboxes are hidden.
- Events carry ids and a little metadata only; read the message itself with
GET /v1/messages/{id}. Reading events does not mark messages as read. - For an
expiredorsuspendedaccount (including asandboxaccount past itsexpires_at) the call never holds:waitis still validated, then treated as 0.
Problem codes: unauthorized (401), invalid_request (400), too_many_waits (429).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
after |
query | integer (0–9007199254740991, default 0) |
no | Return only events with a larger seq. Use the next_after of the previous call. |
wait |
query | integer (0–60, default 0) |
no | Seconds to hold the request while there are no new events. 0 (the default) answers at once. Validated for every account, but treated as 0 for an expired or suspended account. |
limit |
query | integer (1–100, default 100) |
no | Maximum number of events returned. |
Responses
Section titled “Responses”Events after after, oldest first, or none (status is then timeout).
Content type application/json.
| Field | Type | Required | Description |
|---|---|---|---|
status |
string: ok, timeout |
yes | ok: at least one event. timeout: no events were returned. |
events |
array of Event (max 100 items) | yes | Oldest first. |
events[].seq |
integer (1–) | yes | Sequence number, increasing within the account (it can have gaps). |
events[].type |
string | yes | The event type. Only message.received exists today; ignore types you do not know. |
events[].created_at |
string (date-time) | yes | When the event happened (for message.received, when the message was received). |
events[].data |
MessageReceivedEventData | yes | Event data; its shape depends on type. |
events[].data.message_id |
string | yes | |
events[].data.inbox_id |
string | yes | |
events[].data.thread_id |
string | yes | |
events[].data.from |
string or null | yes | As in the message. Not verified. |
events[].data.subject |
string or null | yes | As in the message. Untrusted. |
events[].data.otp_like |
boolean | yes | |
events[].data.sender_trust |
string: aligned_pass, dkim_pass, unauthenticated |
yes | How far the sender is authenticated. Today every message is unauthenticated: aligned_pass and dkim_pass are planned (docs/features/safety.md). They will be based on Cloudflare’s own authentication results, located by position; ambiguous cases are unauthenticated.- aligned_pass: Cloudflare reports a DMARC pass for the domain of from. The mail comes from that domain; its content is still untrusted.• dkim_pass: Cloudflare reports a valid DKIM signature but no DMARC pass. The signature can belong to any domain, so from can still be forged.• unauthenticated: everything else. from can be forged.A message that could not be parsed is always unauthenticated. |
next_after |
integer (0–) | yes | The after value for the next call (the last event’s seq, or after unchanged when there were none). |
Example: Two new messages (after=1)
{ "status": "ok", "events": [ { "seq": 2, "type": "message.received", "created_at": "2026-11-06T10:31:05.902Z", "data": { "message_id": "msg_01M6R6JZDERMT2DVG0T8ABTX4J", "inbox_id": "ibx_01M6R4T180ZADNV1160NS1SRNP", "thread_id": "thr_01M6R6JZDERMT2DVG0T8ABTX4J", "from": "ana@example.com", "subject": "Dinner on Friday?", "otp_like": false, "sender_trust": "unauthenticated" } }, { "seq": 3, "type": "message.received", "created_at": "2026-11-06T10:47:40.117Z", "data": { "message_id": "msg_01M6R7HAANT4D11FHFZPYG0C2V", "inbox_id": "ibx_01M6R4T180ZADNV1160NS1SRNP", "thread_id": "thr_01M6R6JZDERMT2DVG0T8ABTX4J", "from": "ana@example.com", "subject": "Re: Dinner on Friday?", "otp_like": false, "sender_trust": "unauthenticated" } } ], "next_after": 3}Example: No new events (after=3)
{ "status": "timeout", "events": [], "next_after": 3}invalid_request: a query parameter is invalid (after is not an integer from 0 to
9007199254740991, wait is not an integer from 0 to 60, or limit is not an integer from
1 to 100). The message names the parameter.
Problem object (application/problem+json). Codes: invalid_request.
Example: invalid_request: after
{ "type": "https://docs.replyz.io/errors/invalid_request", "code": "invalid_request", "status": 400, "message": "Invalid query parameter \"after\".", "fix": "Use an integer from 0 to 9007199254740991.", "docs": "https://docs.replyz.io/errors/invalid_request.md"}Example: invalid_request: wait
{ "type": "https://docs.replyz.io/errors/invalid_request", "code": "invalid_request", "status": 400, "message": "Invalid query parameter \"wait\".", "fix": "Use an integer from 0 to 60.", "docs": "https://docs.replyz.io/errors/invalid_request.md"}unauthorized: the Authorization header is missing, is not Bearer rz_live_…, or holds an
unknown or revoked key.
| Header | Description |
|---|---|
WWW-Authenticate |
Always Bearer realm="replyz". |
Problem object (application/problem+json). Codes: unauthorized.
Example: unauthorized: missing or malformed key
{ "type": "https://docs.replyz.io/errors/unauthorized", "code": "unauthorized", "status": 401, "message": "Missing or malformed API key.", "fix": "Send the header Authorization: Bearer rz_live_… with the api_key from POST /v1/signup.", "docs": "https://docs.replyz.io/errors/unauthorized.md"}too_many_waits: the account already holds 5 long-polls (wait and events?wait=
together). Wait for one to return, or call without holding.
| Header | Description |
|---|---|
Retry-After |
Always 1. |
Problem object (application/problem+json). Codes: too_many_waits.
Example: too_many_waits
{ "type": "https://docs.replyz.io/errors/too_many_waits", "code": "too_many_waits", "status": 429, "message": "This account already has 5 long-polls open (wait and events share the limit).", "fix": "Wait for one of them to return before starting another, or poll without holding (wait: timeout=0; events: wait=0).", "docs": "https://docs.replyz.io/errors/too_many_waits.md"}internal_error: something failed on Replyz’s side. Retry after a few seconds.
Problem object (application/problem+json). Codes: internal_error.
Example: internal_error
{ "type": "https://docs.replyz.io/errors/internal_error", "code": "internal_error", "status": 500, "message": "Something went wrong on our side.", "fix": "Retry in a few seconds.", "docs": "https://docs.replyz.io/errors/internal_error.md"}