---
title: "List messages"
method: GET
path: "/v1/users/{user_id}/messages"
tags: ["Messages"]
---

# List messages

`GET /v1/users/{user_id}/messages`

## Request

- Base URL: `http://0.0.0.0:3000`
- URL: `http://0.0.0.0:3000/v1/users/{user_id}/messages`
- Auth: none declared

Returns messages for the authenticated user with optional filtering.

**Primary use case**: After calling `/v1/users/:user_id/query`, use `used_message_ids` to fetch full message content.
For non-message sources, use `used_meeting_ids` with `/v1/users/:user_id/meetings/:meeting_id` and `used_event_ids` with `/v1/users/:user_id/events/:event_id`.

**Response fields**:
Every message includes `sender_name` (human-readable name, null if unresolved) and `sent_by` (platform-specific sender identifier: email address for Gmail/Outlook, Slack user ID for Slack, Microsoft Graph user ID or email address for Microsoft Teams; returns 'unknown' if unresolved).

**Expand parameter**:
Heavy fields are not included by default to keep list payloads small. Request them via `expand=`:
- `body_text` — plain-text body
- `body_html` — original HTML body (emails only)
- `raw` — RFC 5322 wire format (emails only; null for non-email providers and messages synced before this was supported)
- `headers` — parsed internet message headers as `[{ name, value }]` (emails only; null when unavailable)

Example: `expand=body_text,raw,headers`.

**Thread bundle (always included)**: `thread_id`, `parent_message_id`, `thread_position`, `conversation_id`, `primary_entity_id`, `entity_ids`. Use these to group messages and cross-reference contacts without extra queries.

**Note on expand behavior**: Single-item endpoints (`GET /v1/users/:user_id/messages/:id`) always return all fields.
List endpoints require `expand` for detailed fields to keep payloads lightweight.

**Filtering**:
- `ids`: Comma-separated message IDs (msg_xxx format) to fetch specific messages
- `integration_id`: Filter to a specific integration (int_xxx format)
- `conversation_id`: Filter to a specific conversation (conv_xxx format)
- `sent_by`: Exact sender identifier (email address for Gmail/Outlook, Slack user ID for Slack, Microsoft Graph user ID or email for Teams)
- `entity_id`: Filter by resolved contact (ent_xxx format from `GET /entities`, or raw UUID)
- `entity_scope`: Controls how `entity_id` matches: "sender" (default) returns messages sent BY entity, "conversation" returns messages where entity is sender or recipient. Requires `entity_id`.
- `start_date`: Messages received after this date (YYYY-MM-DD)
- `end_date`: Messages received before this date (YYYY-MM-DD)
- `exclude_bots`: Exclude bot/automated messages (true/false, default: false)

**Pagination (cursor-only)**:
- `limit`: Maximum number of messages per page (default: 50, max: 100)
- `cursor`: Opaque token from `pagination.next_cursor` of a prior response. Pass it back unchanged to fetch the next page. Omit on the first page.

The response `pagination` object contains `limit`, `has_more`, and (when `has_more=true`) `next_cursor`. There is no `offset` and no `total_count` — partners walk pages forward via cursor and use `has_more` to detect the last page.

**Polling for new messages**:
Use `GET /v1/users/:user_id/sync-stats` to check if new data has arrived (non-metered), then call this endpoint
with `start_date` to fetch recent messages — this endpoint is not metered, unlike `/v1/users/:user_id/query`.

**Examples**:
```
# List messages (lightweight, no body_text)
GET /v1/users/:user_id/messages

# List messages with content
GET /v1/users/:user_id/messages?expand=body_text

# Fetch specific messages with content
GET /v1/users/:user_id/messages?ids=msg_1Z,msg_2B,msg_3C&expand=body_text

# Filter by sender
GET /v1/users/:user_id/messages?sent_by=john@acme.com&expand=body_text

# Filter by resolved contact
GET /v1/users/:user_id/messages?entity_id=ent_xxx&expand=body_text

# Filter by contact - all messages where contact is sender or recipient
GET /v1/users/:user_id/messages?entity_id=ent_xxx&entity_scope=conversation&expand=body_text
```

## Path parameters

- `user_id` string, uuid, required

## Query parameters

- `ids` string
- `integration_id` string
- `conversation_id` string
- `sent_by` string
- `entity_id` string
- `entity_scope` 'sender' | 'conversation'
- `start_date` string, date
- `end_date` string, date
- `exclude_bots` 'true' | 'false'
- `limit` string
- `cursor` string
- `expand` string

## Response `200`

Default Response

- object
  - `success` true, required
  - `data` object[], required
    - `id` string, required
    - `integration_id` string, required
    - `body_text` string, nullable
    - `body_html` string, nullable
    - `raw` string, nullable
    - `headers` object[], nullable
      - `name` string, required
      - `value` string, required
    - `subject` string, nullable, required
    - `received_at` string, required
    - `parent_message_id` string, nullable, required
    - `thread_id` string, nullable, required
    - `thread_position` number, nullable, required
    - `is_bot` boolean, required
    - `conversation_id` string, nullable, required
    - `primary_entity_id` string, nullable, required
    - `entity_ids` string[], required
    - `sender_name` string, nullable, required
    - `sent_by` string, required
    - `display_source` string, nullable, required
  - `pagination` union, required
    - object
      - `limit` number, required
      - `has_more` true, required
      - `next_cursor` string, required
    - object
      - `limit` number, required
      - `has_more` false, required

## Other responses

- `400`: Default Response
- `403`: Default Response
- `404`: Default Response
- `500`: Default Response

---

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