---
title: "POST /messages/search"
method: POST
path: "/messages/search"
---

# POST /messages/search

`POST /messages/search`

## Request

- Base URL: `https://api.useanima.sh/v1`
- URL: `https://api.useanima.sh/v1/messages/search`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

## Request body

- object: Request body for searching messages with full-text query and optional filters
  - `query` string, required: Full-text search query matched against subject, body, and addresses. Supports web-search syntax: bare words are AND-ed (`invoice overdue`), `"quoted phrases"` match in order, `-word` excludes, and `or` alternates (`invoice or receipt`). Words are stemmed, so `approving` matches `approval`. Results are ranked by relevance, with subject matches weighted above body matches above address matches. For meaning-based rather than keyword matching, use POST /messages/search/semantic instead.
  - `filters` object: Optional filters to narrow search results
    - `agentId` string: Filter results by agent ID
    - `channel` 'EMAIL' | 'SMS' | 'MMS' | 'VOICE' | 'WHATSAPP' | 'RCS': Filter results by communication channel
    - `direction` 'INBOUND' | 'OUTBOUND': Filter results by message direction
    - `status` 'QUEUED' | 'SENT' | 'DELIVERED' | 'FAILED' | 'BOUNCED' | 'BLOCKED' | 'PENDING_APPROVAL': Filter results by delivery status
    - `dateRange` object: Filter results within a date range
      - `from` string, date-time: Inclusive start of the date range in ISO 8601 format
      - `to` string, date-time: Inclusive end of the date range in ISO 8601 format
    - `inboxId` string: Filter to one inbox — messages delivered to it (inbound) or sent from it (outbound). The precise way to read a single mailbox when an agent owns several.
    - `fromAddress` string: Filter by sender address, matched exactly (case-sensitive). On inbound mail this is the counterparty; on outbound it is the agent identity the mail was sent from.
    - `toAddress` string: Filter by recipient address, matched exactly (case-sensitive). Note that outbound messages store every recipient in one comma-joined value, so an exact filter matches a multi-recipient send only when given that same joined string — prefer `inboxId` to scope outbound mail to a mailbox.
    - `labels` string[]: Filter to messages carrying ALL of these labels (e.g. `urgent` + `unread` means urgent AND still unread). Case-insensitive. System labels: `unread`, `read`, `archived`, `spam`.
    - `includeSpam` boolean: Include messages classified as spam on arrival. They are excluded by default. Naming `spam` in `labels` also counts as asking for it, so a deliberate spam query is never silently emptied by this default.
    - `includeArchived` boolean: Include messages carrying the `archived` label. They are excluded by default, so archiving actually removes mail from a listing rather than only tagging it. Naming `archived` in `labels` also counts as asking for it (and returns ONLY archived mail); use this flag instead to see archived mail mixed in with the rest.
    - `deleted` 'exclude' | 'include' | 'only': How to treat messages moved to Trash. `exclude` (default) hides them, so deleted mail disappears from every ordinary listing. `only` returns nothing but Trash. `include` ignores the distinction. Deletion is reversible — see DELETE /messages/{id} and POST /messages/{id}/restore.
  - `pagination` object: Pagination parameters for the search results
    - `cursor` string: Opaque cursor from a previous response to fetch the next page
    - `limit` integer: Maximum number of items to return per page (1–100, default 20)

## Response `200`

OK

- object: Ranked, paginated full-text search results
  - `items` object[], required: Matching messages, most relevant first (not most recent first)
    - `id` string, required: Unique identifier of the message
    - `agentId` string, required: ID of the agent that sent or received the message
    - `inboxId` string, nullable, required: ID of the inbox this message belongs to — the inbox it was delivered to (inbound) or sent from (outbound). Null when no inbox backs the address: SMS/MMS/voice messages, and email sent from an email identity that has no inbox (such as a custom-domain identity added to an agent). Pass it to GET /messages?inboxId= to list one inbox's mail.
    - `channel` 'EMAIL' | 'SMS' | 'MMS' | 'VOICE' | 'WHATSAPP' | 'RCS', required: Communication channel used
    - `direction` 'INBOUND' | 'OUTBOUND', required: Whether the message was inbound or outbound
    - `status` 'QUEUED' | 'SENT' | 'DELIVERED' | 'FAILED' | 'BOUNCED' | 'BLOCKED' | 'PENDING_APPROVAL', required: Current delivery status
    - `fromAddress` string, required: Sender address (email or phone number)
    - `toAddress` string, required: Recipient address (email or phone number)
    - `subject` string, nullable, required: Message subject line, or null for channels that do not support subjects
    - `body` string, required: Plain-text content of the message
    - `bodyHtml` string, nullable, required: HTML content of the message, or null if not available
    - `extractedText` string, nullable, required: Plain-text body with the quoted reply chain and signature stripped — just what this sender actually wrote. Read this instead of `body` to avoid re-reading (and paying for) the whole thread on every message. Null when nothing was extracted: non-email channels, messages your agent sent, and mail received before this field existed — so `extractedText ?? body` is always correct. Best-effort and heuristic; `body` remains the verbatim source of truth. Known weak cases: forwards and bottom-posted replies have no separable 'new' part, so this returns the full body rather than nothing.
    - `extractedHtml` string, nullable, required: HTML body with quoted reply chains removed. Same semantics as `extractedText`; `bodyHtml` stays verbatim. Quoting styles that mark the chain with a sibling element rather than wrapping it (Outlook) are not stripped here — `extractedText` still is.
    - `headers` object, nullable, required: Raw message headers as key-value pairs, or null
    - `metadata` object, nullable, required: Arbitrary metadata attached to the message, or null
    - `threadId` string, nullable, required: Thread identifier for conversation grouping, or null
    - `labels` string[], required: Workflow labels on this message. Always contains exactly one of the system labels `unread` or `read`; may also contain `archived`, `spam` (the inbound spam verdict — see `includeSpam` on list/search), and any labels you add yourself. Stored lowercase, deduped, and sorted. Change them with PATCH /messages/{id}/labels.
    - `inReplyTo` string, nullable, required: Message ID this message is replying to, or null
    - `externalId` string, nullable, required: External identifier, or null. For email: the RFC 5322 Message-ID (bare, no angle brackets) — pass it as inReplyTo to reply in-thread. For SMS: the provider-assigned message id.
    - `deletedAt` string, date-time, nullable, required: When this message was moved to Trash, or null if it is live. Deleted messages are hidden from listings unless `deleted` is set to `include` or `only`, and can be brought back with POST /messages/{id}/restore. Nothing purges them.
    - `sentAt` string, date-time, nullable, required: Timestamp when the message was sent, or null if not yet sent
    - `receivedAt` string, date-time, nullable, required: Timestamp when the message was received, or null for outbound messages
    - `readAt` string, date-time, nullable, required: Outbound WhatsApp and RCS messages only: when the recipient's device reported the message as read. Null for inbound messages and for every other channel. Unrelated to the read and unread labels, which record whether the agent has handled a message.
    - `attachments` object[], required: File attachments associated with this message
      - `id` string, required: Unique identifier of the attachment
      - `filename` string, required: Original filename of the attachment
      - `mimeType` string, required: MIME type of the attachment (e.g. application/pdf)
      - `sizeBytes` integer, required: File size in bytes
      - `storageKey` string, required: Internal storage key for retrieving the file
      - `url` string, uri, nullable, required: Pre-signed download URL, or null if not yet generated
      - `createdAt` string, date-time, required: Timestamp when the attachment was created
      - `scanStatus` 'PENDING' | 'CLEAN' | 'FLAGGED' | 'BLOCKED', required: Content-scan verdict. BLOCKED attachments cannot be downloaded — inspect before fetching bytes.
      - `detectedMimeType` string, nullable, required: MIME type inferred from magic bytes; may differ from the declared mimeType
    - `createdAt` string, date-time, required: Timestamp when the message record was created
    - `updatedAt` string, date-time, required: Timestamp when the message record was last updated
    - `snippet` string, nullable, required: Excerpt of the body around the matched terms, with each match wrapped in `<mark>…</mark>`. Lets an agent judge relevance without fetching the full body. Null when the query matched only the subject or an address, so there is nothing in the body to excerpt. Not sanitized: it is a fragment of the raw body, which may itself contain markup.
    - `rank` number, required: Relevance score for this query. Ordering-only — comparable between hits of the SAME search, meaningless across different searches, and not a percentage. Results are already sorted by it, descending.
  - `pagination` object, required: Pagination metadata for retrieving additional pages
    - `nextCursor` string, nullable, required: Cursor to pass as the `cursor` parameter to retrieve the next page, or null if no more results
    - `hasMore` boolean, required: Whether additional pages of results are available

---

[API](https://skmtc.dev/useanima/apis/anima-api.md) · [All operations](https://skmtc.dev/useanima/apis/anima-api/llms.txt) · [OpenAPI document](https://skmtc.dev/useanima/apis/anima-api/revisions/aaa148188980?raw)
