> For the complete documentation index, see [llms.txt](https://docs.material.security/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.material.security/reference/api-v1/messages.md).

# Messages

**Overview**

The Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.

These endpoints make up the Messages API:

* [**Get Message**](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id) (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.
* [**Search Messages**](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search) (`POST /api/v1/messages/search`): starts an asynchronous search.
* [**Get Message Search Results**](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id) (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.
* [**Move Messages to Inbox**](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox) (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.

**Moving Messages Back to the Inbox**

Move to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.

**Response Structure**

Message responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:

* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.
* **`items`**: an array of message objects matching the search query.

Note that search is asynchronous. The [**Search Messages**](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search) endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the [**Get Message Search Results**](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id) response once `status` is `"DONE"`.

**The `include` Parameter**

By default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the [**Get Message**](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id) endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).

**Want a walkthrough?**

Review [Hello World](https://docs.material.security/reference/api-v1/hello-world).

**Pagination and Rate Limiting**

* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.
* The `limit` parameter controls page size on all paginated endpoints.
* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.
* When `hasMore` is false and `nextCursor` is null, you've reached the last page.
* [**Move Messages to Inbox**](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox) is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.

## Get Message

> Retrieves the full details of a single email message by its encoded ID, including sender, recipients, subject, date, and attachment metadata.

```json
{"openapi":"3.1.0","info":{"title":"Material Security API","version":"v1"},"tags":[{"name":"Messages","description":"**Overview**\n\nThe Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.\n\nThese endpoints make up the Messages API:\n\n* **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.  \n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** (`POST /api/v1/messages/search`): starts an asynchronous search.  \n* **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.  \n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.\n\n**Moving Messages Back to the Inbox**\n\nMove to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.\n\n**Response Structure**\n\nMessage responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.  \n* **`items`**: an array of message objects matching the search query.\n\nNote that search is asynchronous. The **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** response once `status` is `\"DONE\"`.\n\n**The `include` Parameter**\n\nBy default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).\n\n**Want a walkthrough?** \n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.\n"}],"servers":[{"url":"https://{domain}","description":"Material Security instance","variables":{"domain":{"default":"your-instance.on.material.security","description":"Your Material Security instance domain"}}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-material-client-secret","description":"Material Security API key"}},"schemas":{"APIMessage":{"title":"APIMessage","description":"An email message.","type":"object","properties":{"id":{"description":"The encoded message ID. Format: `msg.1.<base64>`.","type":"string"},"headers":{"$ref":"#/components/schemas/APIMessageHeaders"},"links":{"description":"The links found in the message body. Populated only when `include=links` is specified.","type":"object","properties":{"meta":{"$ref":"#/components/schemas/CollectionMeta"},"items":{"type":"array","items":{"$ref":"#/components/schemas/APILink"}}},"required":["meta","items"]},"issue":{"description":"The issue associated with this message. Populated only when `include=issue` is specified. Null if no issue is associated.","anyOf":[{"$ref":"#/components/schemas/APIIssueRef"},{"type":"null"}]},"content":{"description":"The body content of the message. Populated only when `include=content` is specified. Null if unavailable.","anyOf":[{"$ref":"#/components/schemas/APIMessageContent"},{"type":"null"}]},"attachments":{"description":"The attachments for this message. Populated only when `include=attachments` is specified.","type":"object","properties":{"meta":{"$ref":"#/components/schemas/CollectionMeta"},"items":{"type":"array","items":{"$ref":"#/components/schemas/APIAttachment"}}},"required":["meta","items"]},"mailboxes":{"description":"The mailboxes in which this message appears. Populated only when `include=mailboxes` is specified.","type":"object","properties":{"meta":{"$ref":"#/components/schemas/CollectionMeta"},"items":{"type":"array","items":{"$ref":"#/components/schemas/APIMailbox"}}},"required":["meta","items"]}},"required":["id","headers"]},"APIMessageHeaders":{"title":"APIMessageHeaders","description":"Header-derived fields for the message.","type":"object","properties":{"messageId":{"description":"The RFC 2822 Message-ID header value for this message.","type":"string"},"date":{"description":"The date the message was sent, in ISO 8601 format.","type":"string","format":"date-time"},"subject":{"description":"The subject line of the email. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]},"from":{"description":"The sender address of the message. Typically the From header, but may be the original sender for messages forwarded through customer-owned infrastructure. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]},"to":{"description":"The recipients on the To header. Addresses only; display names are not included.","type":"array","items":{"type":"string"}},"cc":{"description":"The recipients on the Cc header. Addresses only; display names are not included.","type":"array","items":{"type":"string"}},"bcc":{"description":"The recipients on the Bcc header. Addresses only; display names are not included. May include recipients reconstructed from envelope-level data when the `Bcc:` header was stripped before delivery.","type":"array","items":{"type":"string"}},"authResults":{"description":"Resolved SPF, DKIM, and DMARC results. `null` when no domain context is available to resolve your SEG settings against.","anyOf":[{"$ref":"#/components/schemas/APIAuthResults"},{"type":"null"}]}},"required":["messageId","date","subject","from","to","cc","bcc","authResults"]},"APIAuthResults":{"title":"APIAuthResults","description":"Resolved [authentication results](https://docs.material.security/learn-more/risk-areas/email-threats/investigate/issue-details) (SPF, DKIM, and DMARC) for the message. When you have a Secure Email Gateway (SEG) configured, values are pulled from the SEG’s preserved authentication-results header rather than the inline auth headers.","type":"object","properties":{"spf":{"description":"The resolved SPF authentication result.","anyOf":[{"type":"string","enum":["pass","none","neutral","temperror","permerror","softfail","policy","fail"]},{"type":"null"}]},"dkim":{"description":"The resolved DKIM authentication result.","anyOf":[{"type":"string","enum":["pass","none","neutral","temperror","permerror","policy","fail"]},{"type":"null"}]},"dmarc":{"description":"The resolved DMARC authentication result.","anyOf":[{"type":"string","enum":["pass","none","temperror","permerror","fail"]},{"type":"null"}]}},"required":["spf","dkim","dmarc"]},"CollectionMeta":{"title":"CollectionMeta","type":"object","properties":{"totalCount":{"description":"The total number of items matching the query.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"limit":{"description":"The page size used for this response.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hasMore":{"description":"True if more items exist beyond the current page; false otherwise.","type":"boolean"},"nextCursor":{"description":"The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.","anyOf":[{"type":"string"},{"type":"null"}]}}},"APILink":{"title":"APILink","type":"object","properties":{"href":{"description":"The URL of the link.","type":"string"},"text":{"description":"The visible text of the link. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]},"type":{"description":"What kind of content the link comes from. Null if unavailable.","anyOf":[{"type":"string","enum":["a.href","img.src","script.src"]},{"type":"null"}]}},"required":["href","text","type"]},"APIIssueRef":{"title":"APIIssueRef","type":"object","properties":{"id":{"description":"The encoded ID of the associated issue. Format: `iss.1.<base64>`.","type":"string"}},"required":["id"]},"APIMessageContent":{"title":"APIMessageContent","type":"object","properties":{"parts":{"description":"The content parts that make up the message body.","type":"array","items":{"type":"object","properties":{"mime":{"description":"The MIME type of this content part (for example, `text/html`).","type":"string"},"body":{"description":"The body text of this content part.","type":"string"}},"required":["mime","body"]}}},"required":["parts"]},"APIAttachment":{"title":"APIAttachment","type":"object","properties":{"id":{"description":"The identifier for this attachment.","type":"string"},"filename":{"description":"The original filename of the attachment. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]},"mime":{"description":"The MIME type of the attachment (for example, `application/pdf`).","type":"string"},"size":{"description":"The size of the attachment in bytes.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hashes":{"description":"File hashes, if available","type":"object","properties":{"sha256":{"description":"The SHA-256 hash of the attachment. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]},"md5":{"description":"The MD5 hash of the attachment. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["sha256","md5"]}},"required":["id","filename","mime","size"]},"APIMailbox":{"title":"APIMailbox","type":"object","properties":{"accountId":{"description":"The encoded ID of the account that owns this mailbox. Format: `acct.1.<base64>`.","type":"string"},"email":{"description":"The email address of the mailbox.","type":"string"},"tags":{"description":"The tags associated with this mailbox.","type":"array","items":{"type":"string"}}},"required":["accountId","email","tags"]},"ValidationErrorResponse":{"title":"Validation Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"},"validationFailures":{"description":"List of individual validation failures when request parameters fail schema validation.","type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}}},"required":["error"]},"ErrorResponse":{"title":"Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"}},"required":["error"]}}},"paths":{"/api/v1/messages/{id}":{"get":{"operationId":"getMessage","summary":"Get Message","description":"Retrieves the full details of a single email message by its encoded ID, including sender, recipients, subject, date, and attachment metadata.","tags":["Messages"],"parameters":[{"in":"path","name":"id","schema":{"description":"The encoded ID of the message to retrieve. Format: `msg.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"},"required":true,"description":"The encoded ID of the message to retrieve. Format: `msg.1.<base64>`."},{"in":"query","name":"include","schema":{"description":"A comma-separated list of additional fields to include in the response. Valid values: `content`, `attachments`, `issue`, `mailboxes`, `links`.","type":"string"},"description":"A comma-separated list of additional fields to include in the response. Valid values: `content`, `attachments`, `issue`, `mailboxes`, `links`."}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIMessage"}}}},"400":{"description":"Bad request: invalid parameters or request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"401":{"description":"Unauthorized: missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key doesn't have permission to perform this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found: the requested resource doesn't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests: the client has exceeded a rate limit and should retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Search Messages

> Starts an asynchronous search across ingested email messages using an MQL query and a date range. Returns a search ID (format \`job.1.\<base64>\`) to use when polling for results.

```json
{"openapi":"3.1.0","info":{"title":"Material Security API","version":"v1"},"tags":[{"name":"Messages","description":"**Overview**\n\nThe Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.\n\nThese endpoints make up the Messages API:\n\n* **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.  \n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** (`POST /api/v1/messages/search`): starts an asynchronous search.  \n* **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.  \n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.\n\n**Moving Messages Back to the Inbox**\n\nMove to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.\n\n**Response Structure**\n\nMessage responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.  \n* **`items`**: an array of message objects matching the search query.\n\nNote that search is asynchronous. The **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** response once `status` is `\"DONE\"`.\n\n**The `include` Parameter**\n\nBy default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).\n\n**Want a walkthrough?** \n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.\n"}],"servers":[{"url":"https://{domain}","description":"Material Security instance","variables":{"domain":{"default":"your-instance.on.material.security","description":"Your Material Security instance domain"}}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-material-client-secret","description":"Material Security API key"}},"schemas":{"SearchMessagesBody":{"title":"SearchMessagesBody","description":"Parameters for starting a message search.","type":"object","properties":{"mqlSearchString":{"description":"The MQL (Material Query Language) query string used to filter messages (for example, `from:attacker@example.com subject:\"urgent payment\"`).","type":"string"},"startDate":{"description":"The start of the search time window in ISO 8601 format.","type":"string","format":"date-time"},"endDate":{"description":"The end of the search time window in ISO 8601 format.","type":"string","format":"date-time"},"uDomainIds":{"description":"A list of tenant domain IDs to filter results by. If omitted, searches across all tenants the caller has access to.","type":"array","items":{"type":"string"}}},"required":["mqlSearchString","startDate","endDate"]},"SearchMessagesResponse":{"title":"SearchMessagesResponse","type":"object","properties":{"id":{"description":"The encoded ID of the search. Format: `job.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"},"status":{"description":"The current status of the search. Valid values: `CREATED` (queued), `STARTED` (running), `DONE` (results ready).","type":"string","enum":["CREATED","STARTED","DONE"]}},"required":["id","status"]},"ValidationErrorResponse":{"title":"Validation Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"},"validationFailures":{"description":"List of individual validation failures when request parameters fail schema validation.","type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}}},"required":["error"]},"ErrorResponse":{"title":"Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"}},"required":["error"]}}},"paths":{"/api/v1/messages/search":{"post":{"operationId":"searchMessages","summary":"Search Messages","description":"Starts an asynchronous search across ingested email messages using an MQL query and a date range. Returns a search ID (format `job.1.<base64>`) to use when polling for results.","tags":["Messages"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchMessagesBody"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchMessagesResponse"}}}},"400":{"description":"Bad request: invalid parameters or request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"401":{"description":"Unauthorized: missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key doesn't have permission to perform this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found: the requested resource doesn't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests: the client has exceeded a rate limit and should retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Move Messages to Inbox

> Moves up to 200 messages back to the inbox in every mailbox your token can reach that holds them, clearing the spam, trash, and Surge Protection placements Material applied. Use this to undo a remediation that turned out to be a false positive. The move runs in the background, so a 200 means the request was accepted and queued, not that any message has moved yet. It is best-effort and safe to repeat: each copy is re-checked against the mail provider at move time, and copies already in the inbox, in mailboxes Material can no longer reach, or on unsupported providers are skipped without failing the request. Because of that, \`mailboxCount\` is an upper bound rather than a result. The \`message\_moved\_to\_inbox\` audit log entries carrying a \`uAcctId\` are the authoritative record of what actually moved; one further entry carrying \`numRequested\` is written when the request is queued and records its scope, not that anything moved. Message IDs that do not resolve to a message your token can reach come back in \`notFound\` and are simply not queued; if none of them resolve, the response is a 404 and nothing is queued. Requires the Modify Messages permission. An API token carries the permissions of the user who created it, so a token minted by a user without that permission receives a 403. Requests are rate limited by the number of messages they target, not by request count, against a single organization-wide budget shared by every token; over the limit the response is a 429 with a \`Retry-After\` header.

```json
{"openapi":"3.1.0","info":{"title":"Material Security API","version":"v1"},"tags":[{"name":"Messages","description":"**Overview**\n\nThe Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.\n\nThese endpoints make up the Messages API:\n\n* **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.  \n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** (`POST /api/v1/messages/search`): starts an asynchronous search.  \n* **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.  \n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.\n\n**Moving Messages Back to the Inbox**\n\nMove to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.\n\n**Response Structure**\n\nMessage responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.  \n* **`items`**: an array of message objects matching the search query.\n\nNote that search is asynchronous. The **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** response once `status` is `\"DONE\"`.\n\n**The `include` Parameter**\n\nBy default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).\n\n**Want a walkthrough?** \n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.\n"}],"servers":[{"url":"https://{domain}","description":"Material Security instance","variables":{"domain":{"default":"your-instance.on.material.security","description":"Your Material Security instance domain"}}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-material-client-secret","description":"Material Security API key"}},"schemas":{"MoveMessagesToInboxBody":{"title":"MoveMessagesToInboxBody","type":"object","properties":{"messageIds":{"description":"The encoded IDs of the messages to move back to the inbox. Up to 200 per request.","minItems":1,"maxItems":200,"type":"array","items":{"description":"An encoded message ID. Format: `msg.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"}}},"required":["messageIds"]},"MoveMessagesToInboxResponse":{"title":"MoveMessagesToInboxResponse","type":"object","properties":{"id":{"description":"The encoded ID of the queued move job. Format: `job.1.<base64>`. Quote this when contacting support; it is not pollable through this API.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"},"status":{"description":"Always `CREATED`. The move runs in the background and this endpoint never reports it finishing.","type":"string","const":"CREATED"},"accepted":{"description":"How many of the supplied message IDs resolved to a message you can reach, and were queued for the move.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"notFound":{"description":"The supplied message IDs that did not resolve to a message you can reach. These were not queued. An ID appears here whether the message does not exist or is outside the tenants your token covers.","type":"array","items":{"description":"An encoded message ID. Format: `msg.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"}},"mailboxCount":{"description":"Upper bound on the mailbox copies the move was queued against, summed across accepted messages. Copies already in the inbox are skipped at move time, so fewer may actually change.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","status","accepted","notFound","mailboxCount"]},"ValidationErrorResponse":{"title":"Validation Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"},"validationFailures":{"description":"List of individual validation failures when request parameters fail schema validation.","type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}}},"required":["error"]},"ErrorResponse":{"title":"Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"}},"required":["error"]}}},"paths":{"/api/v1/messages/move-to-inbox":{"post":{"operationId":"moveMessagesToInbox","summary":"Move Messages to Inbox","description":"Moves up to 200 messages back to the inbox in every mailbox your token can reach that holds them, clearing the spam, trash, and Surge Protection placements Material applied. Use this to undo a remediation that turned out to be a false positive. The move runs in the background, so a 200 means the request was accepted and queued, not that any message has moved yet. It is best-effort and safe to repeat: each copy is re-checked against the mail provider at move time, and copies already in the inbox, in mailboxes Material can no longer reach, or on unsupported providers are skipped without failing the request. Because of that, `mailboxCount` is an upper bound rather than a result. The `message_moved_to_inbox` audit log entries carrying a `uAcctId` are the authoritative record of what actually moved; one further entry carrying `numRequested` is written when the request is queued and records its scope, not that anything moved. Message IDs that do not resolve to a message your token can reach come back in `notFound` and are simply not queued; if none of them resolve, the response is a 404 and nothing is queued. Requires the Modify Messages permission. An API token carries the permissions of the user who created it, so a token minted by a user without that permission receives a 403. Requests are rate limited by the number of messages they target, not by request count, against a single organization-wide budget shared by every token; over the limit the response is a 429 with a `Retry-After` header.","tags":["Messages"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveMessagesToInboxBody"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveMessagesToInboxResponse"}}}},"400":{"description":"Bad request: invalid parameters or request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"401":{"description":"Unauthorized: missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key doesn't have permission to perform this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found: the requested resource doesn't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests: the client has exceeded a rate limit and should retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get Message Search Results

> Retrieves the status and results of a message search. Poll this endpoint until \`status\` is \`DONE\`, at which point the response includes a paginated list of matching messages.

```json
{"openapi":"3.1.0","info":{"title":"Material Security API","version":"v1"},"tags":[{"name":"Messages","description":"**Overview**\n\nThe Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.\n\nThese endpoints make up the Messages API:\n\n* **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.  \n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** (`POST /api/v1/messages/search`): starts an asynchronous search.  \n* **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.  \n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.\n\n**Moving Messages Back to the Inbox**\n\nMove to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.\n\n**Response Structure**\n\nMessage responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.  \n* **`items`**: an array of message objects matching the search query.\n\nNote that search is asynchronous. The **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** response once `status` is `\"DONE\"`.\n\n**The `include` Parameter**\n\nBy default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).\n\n**Want a walkthrough?** \n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.\n"}],"servers":[{"url":"https://{domain}","description":"Material Security instance","variables":{"domain":{"default":"your-instance.on.material.security","description":"Your Material Security instance domain"}}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-material-client-secret","description":"Material Security API key"}},"schemas":{"MessageSearchResponse":{"title":"MessageSearchResponse","type":"object","properties":{"id":{"description":"The encoded ID of the search. Format: `job.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"},"status":{"description":"The current status of the search. Valid values: `CREATED` (queued), `STARTED` (running), `DONE` (results ready).","type":"string","enum":["CREATED","STARTED","DONE"]},"data":{"description":"The search results. Present only when `status` is `\"DONE\"`.","type":"object","properties":{"meta":{"description":"The pagination metadata for the search results, including `totalCount`, `limit`, `hasMore`, `nextCursor`, and `truncated`.","type":"object","properties":{"totalCount":{"description":"The total number of items matching the query.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"limit":{"description":"The page size used for this response.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hasMore":{"description":"True if more items exist beyond the current page; false otherwise.","type":"boolean"},"nextCursor":{"description":"The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.","anyOf":[{"type":"string"},{"type":"null"}]},"truncated":{"description":"True when the query matched more messages than the search returns, so `items` is a prefix of the real result set and `totalCount` reports the cap rather than the real match count. Narrow the query or the date range to see the rest.","type":"boolean"}}},"items":{"description":"The list of messages matching the search query.","type":"array","items":{"$ref":"#/components/schemas/APIMessageItem"}}},"required":["meta","items"]}},"required":["id","status"]},"APIMessageItem":{"title":"APIMessageItem","type":"object","properties":{"id":{"description":"The encoded message ID. Format: `msg.1.<base64>`.","type":"string"},"headers":{"$ref":"#/components/schemas/APIMessageItemHeaders"}},"required":["id"]},"APIMessageItemHeaders":{"title":"APIMessageItemHeaders","type":"object","properties":{"date":{"description":"The date the message was sent, in ISO 8601 format.","type":"string","format":"date-time"},"from":{"description":"The sender address of the message. Null if unavailable.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["date","from"]},"ValidationErrorResponse":{"title":"Validation Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"},"validationFailures":{"description":"List of individual validation failures when request parameters fail schema validation.","type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}}},"required":["error"]},"ErrorResponse":{"title":"Error Response","type":"object","properties":{"error":{"description":"A human-readable description of the error.","type":"string"}},"required":["error"]}}},"paths":{"/api/v1/messages/search/{id}":{"get":{"operationId":"getMessageSearch","summary":"Get Message Search Results","description":"Retrieves the status and results of a message search. Poll this endpoint until `status` is `DONE`, at which point the response includes a paginated list of matching messages.","tags":["Messages"],"parameters":[{"in":"path","name":"id","schema":{"description":"The encoded ID of the search job to retrieve. Format: `job.1.<base64>`.","type":"string","pattern":"^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"},"required":true,"description":"The encoded ID of the search job to retrieve. Format: `job.1.<base64>`."},{"in":"query","name":"cursor","schema":{"description":"The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.","type":"string"},"description":"The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."},{"in":"query","name":"limit","schema":{"description":"The maximum number of items to return per page.","type":"integer"},"description":"The maximum number of items to return per page."}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageSearchResponse"}}}},"400":{"description":"Bad request: invalid parameters or request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"401":{"description":"Unauthorized: missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden: the API key doesn't have permission to perform this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found: the requested resource doesn't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many requests: the client has exceeded a rate limit and should retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.material.security/reference/api-v1/messages.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
