> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vambe.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Retrieve messages by AI contact with pagination

> Paginated message history, newest first. Prefer this over v2: `page` is 1-based and echoed back unchanged, `page_size` is configurable, and `total`/`pages` describe exactly what `data` contains — `data.length` never exceeds `page_size`, and summing `data` across all pages equals `total`. Unlike v2, the response contains only real messages: the synthetic "stage" and "interactive_note" entries that v2 injects are not messages and are not returned here. Also unlike v2, this endpoint is side-effect free: reading messages does NOT mark the conversation as read. Requesting a page beyond the last one returns an empty `data` with `has_next_page: false`.

## Overview

Retrieve a contact's messages with pagination, **newest first**. This is the recommended endpoint for reading message history.

Use this instead of [v2](/reference/contact/get-messages-paginated): `page` is 1-based and echoed back unchanged, `page_size` is configurable, and the counters describe exactly what you receive.

## Authentication

Include your API key in the request header:

```
x-api-key: your_api_key_here
```

## Path Parameters

| Parameter     | Type          | Required | Description                      |
| ------------- | ------------- | -------- | -------------------------------- |
| `aiContactId` | string (UUID) | Yes      | Unique identifier of the contact |

## Query Parameters

| Parameter   | Type   | Required | Default | Description                                        |
| ----------- | ------ | -------- | ------- | -------------------------------------------------- |
| `page`      | number | No       | `1`     | Page number, **1-based**. Page 1 = newest messages |
| `page_size` | number | No       | `15`    | Messages per page, between `1` and `100`           |

Both parameters must be integers. `page` below 1, `page_size` outside 1–100, and fractional values are rejected with `400`.

## Response Structure

| Field           | Type    | Description                                                   |
| --------------- | ------- | ------------------------------------------------------------- |
| `data`          | array   | Messages for this page, ordered newest first                  |
| `page`          | number  | The page you requested, echoed back                           |
| `page_size`     | number  | The page size in effect                                       |
| `pages`         | number  | Total number of pages. Valid values for `page` are `1..pages` |
| `total`         | number  | Total messages for this contact across all pages              |
| `has_next_page` | boolean | Whether a page after this one exists                          |

<Note>
  The counters are self-consistent: `data` never holds more than `page_size`
  items, and summing `data` across all pages equals `total`.
</Note>

### Message Object

This list is **exhaustive** and identical on every channel — the response carries these fields and nothing else:

| Field                 | Type                    | Description                                                                                                                                                                         |
| --------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                  | string                  | Message identifier, always a string (some channels store it as an integer internally)                                                                                               |
| `created_at`          | string (ISO)            | Message timestamp                                                                                                                                                                   |
| `body`                | string                  | Message text                                                                                                                                                                        |
| `direction`           | string                  | `inbound`, `outbound`, `internal_note`, `ai_note` or `task`                                                                                                                         |
| `type`                | string                  | Channel message type, e.g. `text`, `image`, `audio`, `document`                                                                                                                     |
| `content`             | object \| array \| null | Structured payload for non-text messages, as delivered by the channel. Channel-defined shape — WhatsApp template messages carry an array of components. Do not assume a fixed shape |
| `provider_message_id` | string \| null          | Channel provider's own message id                                                                                                                                                   |
| `attribution_id`      | string \| null          | Attribution identifier, when set                                                                                                                                                    |
| `user`                | object \| null          | Human sender (`first_name`, `last_name`) when sent by an agent                                                                                                                      |
| `assistant`           | object \| null          | AI sender (`id`, `name`) when sent by an assistant                                                                                                                                  |
| `campaign`            | object \| null          | `{ title }` when the message came from a campaign                                                                                                                                   |
| `ticket_id`           | string \| null          | Ticket this message belongs to, when there is one                                                                                                                                   |

<Note>
  v3 returns a **fixed projection**, so integrating once works for every
  channel. v2 instead returns the raw channel row — on WhatsApp that means the
  whole message record nested under `whatsapp_message` on top of the flattened
  fields, plus the ticket, summary and activity graph. If you depend on a field
  that only exists there, tell us and we will consider adding it to v3 rather
  than exposing the internal row.
</Note>

<Note>
  Unlike v2, the response contains **only real messages**. The synthetic `stage`
  and `interactive_note` entries that v2 mixes into its list are not messages
  and are not returned here.
</Note>

## Example Request

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contact/v3/df980fc8-b6db-4820-bf22-2969482d106d/messages?page=1&page_size=15' \
  --header 'x-api-key: your_api_key_here'
```

## Example Response

```json theme={null}
{
  "data": [
    {
      "id": "9f1c2b3a-5d6e-4f70-8a91-2b3c4d5e6f70",
      "created_at": "2024-09-30T10:01:30.000Z",
      "body": "¡Por supuesto! ¿Cuál es tu número de pedido?",
      "direction": "outbound",
      "type": "text",
      "content": null,
      "provider_message_id": "wamid.HBgLNTY5MTIzNDU2NzgVAgARGBI5QTND",
      "attribution_id": null,
      "user": null,
      "assistant": {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "name": "Vambe"
      },
      "campaign": null,
      "ticket_id": "7c3e4f21-9b8a-4d5e-8f60-1a2b3c4d5e6f"
    },
    {
      "id": "6b0d8e1f-2a34-4c56-9d78-0e1f2a3b4c56",
      "created_at": "2024-09-30T10:00:00.000Z",
      "body": "Hola, necesito ayuda con mi pedido",
      "direction": "inbound",
      "type": "text",
      "content": null,
      "provider_message_id": "wamid.HBgLNTY5ODc2NTQzMjEVAgASGBQzQjJE",
      "attribution_id": null,
      "user": null,
      "assistant": null,
      "campaign": null,
      "ticket_id": "7c3e4f21-9b8a-4d5e-8f60-1a2b3c4d5e6f"
    }
  ],
  "page": 1,
  "page_size": 15,
  "pages": 9,
  "total": 127,
  "has_next_page": true
}
```

## Fetching the Full History

```javascript theme={null}
const getAllMessages = async (contactId, apiKey) => {
  const all = [];
  let page = 1;

  while (true) {
    const response = await fetch(
      `https://api.vambe.me/api/public/contact/v3/${contactId}/messages?page=${page}&page_size=100`,
      { headers: { 'x-api-key': apiKey } },
    );

    const result = await response.json();
    all.push(...result.data);

    if (!result.has_next_page) {
      return all; // all.length === result.total
    }

    page += 1;
  }
};
```

Requesting a page past the last one is not an error: it returns an empty `data` with `has_next_page: false`.

## Error Responses

| Status Code | Description                                  |
| ----------- | -------------------------------------------- |
| 400         | Invalid `page` or `page_size`                |
| 401         | Unauthorized — invalid or missing API key    |
| 404         | Contact not found for this API key's account |
| 500         | Internal Server Error                        |

## Important Notes

* **Order is newest first.** Page 1 holds the most recent messages.
* **Reading is side-effect free.** Unlike v2, fetching messages here does **not** mark the conversation as read, so polling this endpoint never changes what your agents see as unread.
* **Deleted Instagram messages are excluded** from `data`, `total` and `pages`.
* **Cross-channel**: works for every channel a contact may use.

## Related Endpoints

* [GET /api/public/contact/v2/{aiContactId}/messages](/reference/contact/get-messages-paginated) — legacy paginated history
* [GET /api/public/contact/{aiContactId}/conversations](/reference/contact/get-conversations) — messages grouped into conversations
* [GET /api/public/contact/{aiContactId}/info](/reference/contact/get-info-of-an-ai-contact) — contact info


## OpenAPI

````yaml get /api/public/contact/v3/{aiContactId}/messages
openapi: 3.0.0
info:
  title: Vambe AI API
  description: Vambe AI documentation
  version: '1.0'
  contact: {}
servers:
  - url: https://api.vambe.me
    description: Production Server
security: []
tags:
  - name: Vambe AI
    description: ''
paths:
  /api/public/contact/v3/{aiContactId}/messages:
    get:
      tags:
        - Contact
      summary: Retrieve messages by AI contact with pagination
      description: >-
        Paginated message history, newest first. Prefer this over v2: `page` is
        1-based and echoed back unchanged, `page_size` is configurable, and
        `total`/`pages` describe exactly what `data` contains — `data.length`
        never exceeds `page_size`, and summing `data` across all pages equals
        `total`. Unlike v2, the response contains only real messages: the
        synthetic "stage" and "interactive_note" entries that v2 injects are not
        messages and are not returned here. Also unlike v2, this endpoint is
        side-effect free: reading messages does NOT mark the conversation as
        read. Requesting a page beyond the last one returns an empty `data` with
        `has_next_page: false`.
      operationId: PublicAiContactController_getMessagesV3
      parameters:
        - name: x-api-key
          in: header
          description: API key required to authorize the request
          required: true
          schema:
            type: string
        - name: aiContactId
          required: true
          in: path
          description: ID of the AI contact
          schema:
            type: string
        - name: page
          required: false
          in: query
          description: >-
            Page number, 1-based. Page 1 returns the newest messages. Defaults
            to 1.
          schema:
            minimum: 1
            maximum: 9007199254740991
            default: 1
            example: 1
            type: number
        - name: page_size
          required: false
          in: query
          description: Messages per page, between 1 and 100. Defaults to 15.
          schema:
            minimum: 1
            maximum: 100
            default: 15
            example: 15
            type: number
      responses:
        '200':
          description: Messages retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    description: >-
                      Messages for this page, newest first. The shape below is
                      exhaustive and identical across channels: no
                      channel-specific or internal fields are returned.
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: >-
                            Always a string, including on channels whose ids are
                            numeric internally.
                          example: df980fc8-b6db-4820-bf22-2969482d106d
                        created_at:
                          type: string
                          example: '2024-09-30T10:00:00.000Z'
                        body:
                          type: string
                          example: Hola, necesito ayuda
                        direction:
                          type: string
                          enum:
                            - inbound
                            - outbound
                            - internal_note
                            - ai_note
                            - task
                          example: inbound
                        type:
                          type: string
                          example: text
                        content:
                          description: >-
                            Structured payload for non-text messages, as
                            delivered by the channel. Shape is channel-defined:
                            an object, an array (WhatsApp template components)
                            or null. Do not assume a fixed shape.
                        provider_message_id:
                          type: string
                          nullable: true
                          example: wamid.HBgLNTY5MTIzNDU2NzgVAgARGBI5QTND
                        attribution_id:
                          type: string
                          nullable: true
                        user:
                          type: object
                          nullable: true
                          properties:
                            first_name:
                              type: string
                              example: Jane
                            last_name:
                              type: string
                              example: Doe
                        assistant:
                          type: object
                          nullable: true
                          properties:
                            id:
                              type: string
                              example: 123e4567-e89b-12d3-a456-426614174000
                            name:
                              type: string
                              example: Vambe
                        campaign:
                          type: object
                          nullable: true
                          properties:
                            title:
                              type: string
                              example: Reactivación agosto
                        ticket_id:
                          type: string
                          nullable: true
                          description: Ticket this message belongs to, when there is one.
                          example: 7c3e4f21-9b8a-4d5e-8f60-1a2b3c4d5e6f
                  page:
                    type: number
                    description: The requested page, echoed back.
                    example: 1
                  page_size:
                    type: number
                    example: 15
                  pages:
                    type: number
                    description: >-
                      Total number of pages. Valid values for `page` are
                      1..pages.
                    example: 3
                  total:
                    type: number
                    description: Total messages for this contact, across all pages.
                    example: 45
                  has_next_page:
                    type: boolean
                    example: true
        '400':
          description: Invalid pagination parameters.
        '404':
          description: AI contact not found.

````