---
title: "List events"
method: GET
path: "/v1/events"
tags: ["Events"]
---

# List events

`GET /v1/events`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/events`
- Auth: HTTP bearer

**Requires scope:** `read`.

## Query parameters

- `limit` integer
- `cursor` string
- `event_type` string
- `agent_id` string
- `since` string, date-time

## Response `200`

A page of typed events.

- object
  - `data` Event[], required: The items on this page, at most `limit` of them.
    - `id` string, required: Event id (`evt_…`). A webhook delivery carries it as `id` and in the `X-Mails-Event-Id` header, the same on every retry and redelivery.
    - `type` string, required: Event type. The types emitted: message.sent, message.scheduled, message.delivered, message.delayed, message.bounced, message.complained, message.failed, message.received, message.received.unauthenticated, message.received.over_limit, reply.received, draft.scheduled, draft.sending, draft.sent, draft.failed, suppression.added, webhook.test, thread.created, thread.updated, thread.deleted. `message.sent` is raised for every message that goes out, a scheduled draft's included (which raises `draft.sent` too). `message.scheduled` is raised for every message scheduled for later (by a send, a batch item or a reply), and again when one is rescheduled, with the new `scheduled_at` and the `previous_scheduled_at` it replaced. `message.delivered` is raised once per recipient when the receiving side reports the delivery, and at once for a send recorded as `delivered` when it was sent. `message.delayed` reports a transient bounce (the message may still be delivered), once per recipient however often the delay is reported; a webhook endpoint gets it, like the thread events, only when it lists it by name. `message.received.over_limit` reports mail received while the workspace was over a limit; its data says which (`over_limit`), whether the text was kept (`content_kept`), and who sent it (`from` and `envelope_from`, as on the inbound events), and carries no subject or body: the injection scan did not read that mail. For the same reason `thread.created` for received mail gives its `subject` only when the scan read the mail and did not quarantine it, and null otherwise.
    - `workspace_id` string, required: The workspace the event belongs to (`wsk_…`).
    - `agent_id` string: The agent the event is about (`agt_…`). Absent when it has none, as on `webhook.test`.
    - `thread_id` string: The thread the message behind the event belongs to (`thrd_…`), when it has one.
    - `source_message_id` string: The message the event is about: the sent message (`msg_…`) on events about a send, the received one (`rcv_…`) on inbound events. Absent on events that carry no message, such as the thread events and `webhook.test`.
    - `intent` string: What an inbound message wants, on `message.received`, `reply.received` and `message.received.unauthenticated` only. Fixed values when it was not read for intent: `general_reply` for a reply in a thread, `unknown` when the rules layer settled it, `unclassified` when the agent's `classify_inbound` is off or the classifier could not run.
    - `urgency` number: Urgency score 0–1 (higher = more time-sensitive).
    - `injection_score` number: Prompt-injection score 0–1 (inbound events). The scan reads the subject and the first 100,000 characters of the body: the openings of its text part, of its HTML part's text and of the text that HTML hides come first, then the rest of each. Anything past 100,000 characters is not scanned.
    - `sender_reputation` number: Trust score for the sender of an inbound message, 0–1 (higher = more trusted), on the same events as `intent`. The classifier's estimate when it read the message for intent; otherwise a preset, such as 0.3 for a sender that failed authentication.
    - `entities` object: Extracted entities as key→value pairs (object) — present when intent classification ran.
    - `injection_categories` string[]: Prompt-injection categories flagged — single-event GET only.
    - `classifier_model` string: What scored the message: a model name; a value starting `rules-layer` when the rules layer settled the intent; `injection-scan-only`; or `scan-unavailable` when the scan could not run. On every event read and in webhook deliveries.
    - `quarantined` boolean: Inbound events only: the firewall's verdict, true when the message's injection score crossed the quarantine threshold. `injection_score` is the evidence; this is the decision.
    - `test_mode` boolean, required: True when a test key (`mk_test_…`) or test mail raised the event, and on every `webhook.test` event. `thread.updated` and `thread.deleted` take the thread's own mode instead: true when the thread holds no live mail.
    - `data` object: Full typed-event payload (shape varies by type). Inbound events (`message.received`, `reply.received`, `message.received.unauthenticated`) carry `from`, the first mailbox of the email's From header, which the checks below are about (it is proven only when `dmarc` is `pass`; when no raw email reached us it is read from the From header our mail server passed on, and the envelope sender stands in only when there was none), and `envelope_from`, the envelope sender (SMTP MAIL FROM), where bounces go and which a sending service often sets to its own bounce address: replies go to the Reply-To or to `from`, never there. They carry the sender's authentication as we checked it from the email itself, never from a header in it: `dkim` (`pass` when a DKIM signature verifies, whichever domain signed it; `fail`; `none` for an unsigned email; or `unknown` when it could not be checked), `dkim_domain` (the domain of a passing DKIM signature that matches the domain of the From header, or null), `dmarc` (`none` when the From domain publishes no DMARC record; otherwise `pass` when a DKIM signature aligned with the From domain verifies, and `unknown`, never `fail`, when none does: SPF is not checked, and a sender that relies on SPF alone is not evidence of forgery) and `spf` (`unknown`: mail reaches us through Cloudflare, and the sending server's address is not known). `message.received.unauthenticated` replaces `message.received` or `reply.received` when no DKIM signature aligned with the From domain verifies and the From domain's DMARC policy is quarantine or reject: those domains promise signed mail, so this is the forged-sender case, and `auth_failed` is true. Every other sender gets no such event. `spf_pass` and `dkim_pass` are the same as true, false or null. Mail forwarded to an agent (forwarding mode) is judged the same way, since a forward that leaves the message as it was keeps the sender's signature (its `envelope_from` is the forwarding service's own address); `arc` is the ARC chain as we checked it (`pass`, `fail`, `none` or `unknown`), and `trusted_forwarder` names the mail service (`google_workspace`, `microsoft_365`, `fastmail` or `zoho`) whose ARC seal, on a chain that validates, reports that DMARC passed for the From domain when the mail reached it, or null. Such mail never gets `message.received.unauthenticated`, even when the forward broke the sender's signature; `dkim`, `dkim_domain` and `dmarc` still say only what we verified ourselves.
    - `created_at` string, date-time, required: When the event was recorded (UTC); the `since` filter of `GET /v1/events` compares against it.
  - `has_more` boolean, required: True when more items follow this page: pass `next_cursor` as `cursor` to get them.
  - `next_cursor` string: Pass as `cursor` to fetch the next page. Absent on the last page.

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `409`: Conflict — duplicate resource, an in-progress idempotent request, a draft or scheduled message that started sending while it was being changed, or a thread deleted for good while one of its messages is being sent (`duplicate_resource`); or an agent name more than one agent has (`agent_name_conflict`: use the agent's id); or a status change on an archived agent, since archiving cannot be undone (`agent_archived`).
- `500`: Internal server error.

---

[API](https://skmtc.dev/mails/apis/mails-ai-api.md) · [All operations](https://skmtc.dev/mails/apis/mails-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mails/apis/mails-ai-api/revisions/4d4930a8c33b?raw)
