# POST /v1/messages/search

Search the message log

The messages of the server, newest first, one page at a time. A POST so that an address travels in the body and never in a URL. Messages older than the retention window are gone.

Takes a server key (`sk_…`, or `sk_test_…` on a test server) as a bearer token. TypeScript: `sendora.messages.search(filters)`. Python: `sendora.messages.search(**filters)`.

## Request body

- `recipient` (string (email)): Messages to this address, in to, cc or bcc.
- `streamId` (string (uuid)): Messages on this stream of the server.
- `broadcastId` (string (uuid)): Messages of this broadcast.
- `tag` (string (at least 1, at most 100 characters))
- `status` ("queued" | "delivered" | "deferred" | "bounced" | "expired"): Messages with at least one recipient in this state.
- `from` (string (date-time)): Submitted at or after this time.
- `to` (string (date-time)): Submitted before this time.
- `limit` (integer (1 to 100)): Page size, 1 to 100.
- `after` (string (uuid)): The `next` value of the previous page.

Example:

```json
{
  "recipient": "anna@example.com",
  "status": "bounced",
  "limit": 20
}
```

## Responses

### 200 A page of the messages that match, newest first.

- `messages` (array of object, required)
  - `messageId` (string (uuid), required)
  - `streamId` (string (uuid), required): The stream the message went on.
  - `broadcastId` (string (uuid) or null, required): The broadcast the message belongs to; null for a message sent on its own.
  - `status` ("accepted" | "injected" | "failed", required): `accepted` until handed to the mail server, `injected` after; `failed` if that never worked.
  - `from` (string, required)
  - `subject` (string, required)
  - `tag` (string or null, required)
  - `metadata` (map of string to string, required)
  - `submittedAt` (string (date-time), required)
  - `test` (boolean, required): True when the message went through a test server: its events were simulated and nobody received it.
  - `recipients` (array of object, required)
    - `address` (string, required)
    - `kind` ("to" | "cc" | "bcc", required)
    - `status` ("queued" | "delivered" | "deferred" | "bounced" | "expired", required): queued until the receiver answers; deferred while it keeps saying try later.
- `next` (string (uuid) or null, required): Pass as `after` for the next page; null on the last.

Example:

```json
{
  "messages": [
    {
      "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
      "streamId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "broadcastId": null,
      "status": "injected",
      "from": "no-reply@example.se",
      "subject": "Your invoice for September",
      "tag": "invoice",
      "metadata": {
        "invoiceId": "2026-0912"
      },
      "submittedAt": "2026-09-15T12:00:00.000Z",
      "test": false,
      "recipients": [
        {
          "address": "anna@example.com",
          "kind": "to",
          "status": "delivered"
        }
      ]
    }
  ],
  "next": null
}
```

## Errors

Every error answers `error`, the code, and `message`, a sentence for a person. A code that adds fields is shown in full below the table.

| Code | Status | Meaning |
| --- | --- | --- |
| `invalid_request` | 400 | The body, the query or a header does not match what the route takes. |
| `unauthorized` | 401 | The key is missing, malformed or revoked. |
| `wrong_token_kind` | 403 | The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes. |

### `invalid_request`

- `issues` (array of object): One entry per invalid field; absent when a header is wrong.
  - `path` (string, required): The field, dotted, such as to.0.email; empty when the whole body is wrong.
  - `message` (string, required)

Example:

```json
{
  "error": "invalid_request",
  "message": "The request is invalid: to.0: Invalid email address",
  "issues": [
    {
      "path": "to.0",
      "message": "Invalid email address"
    }
  ]
}
```
