> ## 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.

# Get contacts by time filters

> Retrieve contacts filtered by activity (last message within N days), creation time, or update time. Works across all channels (WhatsApp, Instagram, etc.). At least one of days, created_after, or updated_after is required. For incremental sync, poll with updated_after set to the latest updated_at you have stored: the response includes both created_at and updated_at so you can tell new contacts (created_at > your cursor) apart from updated ones. Results are ordered ascending by updated_at (or created_at) when those filters are used, so you can page forward with limit/offset and advance your cursor safely. Optionally filter by custom field values.

## Overview

Retrieve contacts that had their last message within a specified number of days. This endpoint works across all communication channels (WhatsApp, Instagram, Web Chat, SMS, etc.) providing a unified view of recent contact activity.

Perfect for identifying active contacts, tracking engagement, and filtering contacts by recency across your entire customer base.

## Use Cases

* **Active Contacts Dashboard**: Show contacts who messaged in the last 7, 30, or 90 days
* **Engagement Metrics**: Track how many contacts are actively engaging
* **Re-engagement Campaigns**: Find contacts who haven't messaged recently
* **Activity Reports**: Generate reports of contact activity over time
* **Cross-Channel Analytics**: See all active contacts regardless of channel
* **CRM Sync**: Export recently active contacts to external systems

## Authentication

This endpoint requires authentication using an API key. Include your API key in the request header:

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

## Query Parameters

| Parameter     | Type   | Required | Description                                       |
| ------------- | ------ | -------- | ------------------------------------------------- |
| `days`        | number | Yes      | Number of days to filter contacts (1-365)         |
| `stage_id`    | string | No       | Stage ID (UUID) to filter contacts by stage       |
| `pipeline_id` | string | No       | Pipeline ID (UUID) to filter contacts by pipeline |

## Response Structure

Returns an array of contact objects:

| Field              | Type           | Description                                       |
| ------------------ | -------------- | ------------------------------------------------- |
| `id`               | string (UUID)  | Unique identifier for the contact                 |
| `name`             | string         | Contact's full name                               |
| `phone`            | string \| null | Contact's phone number                            |
| `email`            | string \| null | Contact's email address                           |
| `platform`         | string         | Communication channel (whatsapp, instagram, etc.) |
| `last_message_at`  | string (ISO)   | Timestamp of the last message sent/received       |
| `chat_status`      | string         | Current chat status (UNATTENDED, ATTENDED, etc.)  |
| `created_at`       | string (ISO)   | Contact creation timestamp                        |
| `client_id`        | string (UUID)  | Your organization ID                              |
| `ai_customer_id`   | string (UUID)  | Associated customer profile ID                    |
| `default_stage_id` | string (UUID)  | Contact's default pipeline stage                  |
| `blocked`          | boolean        | Whether the contact is blocked                    |
| `is_chat_read`     | boolean        | Whether the chat has been read by an agent        |
| `active_ticket_v2` | object \| null | Active ticket information (see below)             |

### Active Ticket Object (`active_ticket_v2`)

| Field              | Type           | Description                              |
| ------------------ | -------------- | ---------------------------------------- |
| `id`               | string (UUID)  | Unique identifier for the ticket         |
| `current_stage_id` | string \| null | Current pipeline stage ID for the ticket |

## Example Request

### Basic Request (All Contacts)

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts?days=30' \
  --header 'x-api-key: your_api_key_here'
```

### Request with Stage Filter

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts?days=30&stage_id=123e4567-e89b-12d3-a456-426614174000' \
  --header 'x-api-key: your_api_key_here'
```

### Request with Pipeline Filter

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts?days=30&pipeline_id=123e4567-e89b-12d3-a456-426614174000' \
  --header 'x-api-key: your_api_key_here'
```

## Example Response

```json theme={null}
[
  {
    "id": "df980fc8-b6db-4820-bf22-2969482d106d",
    "name": "Maria Rodriguez",
    "phone": "+56912345678",
    "email": "maria.rodriguez@example.com",
    "platform": "whatsapp",
    "last_message_at": "2024-09-30T14:25:33.000Z",
    "chat_status": "ATTENDED",
    "created_at": "2024-01-15T10:00:00.000Z",
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "ai_customer_id": "660e8400-e29b-41d4-a716-446655440001",
    "default_stage_id": "770e8400-e29b-41d4-a716-446655440002",
    "blocked": false,
    "is_chat_read": true,
    "active_ticket_v2": {
      "id": "bb0e8400-e29b-41d4-a716-446655440006",
      "current_stage_id": "cc0e8400-e29b-41d4-a716-446655440007"
    }
  },
  {
    "id": "880e8400-e29b-41d4-a716-446655440003",
    "name": "Carlos Silva",
    "phone": "+56987654321",
    "email": null,
    "platform": "instagram",
    "last_message_at": "2024-09-29T18:10:22.000Z",
    "chat_status": "UNATTENDED",
    "created_at": "2024-03-20T09:30:00.000Z",
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "ai_customer_id": "990e8400-e29b-41d4-a716-446655440004",
    "default_stage_id": "aa0e8400-e29b-41d4-a716-446655440005",
    "blocked": false,
    "is_chat_read": false,
    "active_ticket_v2": {
      "id": "dd0e8400-e29b-41d4-a716-446655440008",
      "current_stage_id": null
    }
  }
]
```

## Common Use Cases

### 1. Get Contacts from Last 7 Days

```javascript theme={null}
const getRecentContacts = async () => {
  const response = await fetch(
    'https://api.vambe.me/api/public/contacts?days=7',
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();
  console.log(`Found ${contacts.length} contacts active in the last 7 days`);

  return contacts;
};
```

### 2. Track Engagement by Channel

```javascript theme={null}
const getContactsByChannel = async (days) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/contacts?days=${days}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();

  // Group by platform
  const byChannel = contacts.reduce((acc, contact) => {
    const channel = contact.platform || 'unknown';
    if (!acc[channel]) acc[channel] = [];
    acc[channel].push(contact);
    return acc;
  }, {});

  console.log('Contacts by channel:', {
    whatsapp: byChannel.whatsapp?.length || 0,
    instagram: byChannel.instagram?.length || 0,
    webchat: byChannel.webchat?.length || 0,
  });

  return byChannel;
};
```

### 3. Find Unattended Contacts

```javascript theme={null}
const getUnattendedContacts = async (days) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/contacts?days=${days}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();

  // Filter unattended
  const unattended = contacts.filter(
    (c) => c.chat_status === 'UNATTENDED' && !c.is_chat_read,
  );

  console.log(`${unattended.length} unattended contacts need attention`);

  return unattended;
};
```

### 4. Re-engagement Campaign Data

```javascript theme={null}
const getContactsForReengagement = async () => {
  // Get contacts from 30-60 days ago (active before but not recently)
  const recent30 = await fetch(
    'https://api.vambe.me/api/public/contacts?days=30',
    {
      headers: { 'x-api-key': 'your_api_key_here' },
    },
  ).then((r) => r.json());

  const recent60 = await fetch(
    'https://api.vambe.me/api/public/contacts?days=60',
    {
      headers: { 'x-api-key': 'your_api_key_here' },
    },
  ).then((r) => r.json());

  // Contacts in 60 days but not in 30 days
  const recent30Ids = new Set(recent30.map((c) => c.id));
  const reengageTargets = recent60.filter((c) => !recent30Ids.has(c.id));

  console.log(`${reengageTargets.length} contacts for re-engagement campaign`);

  return reengageTargets;
};
```

### 5. Export to External CRM

```javascript theme={null}
const exportRecentContactsToCRM = async (days) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/contacts?days=${days}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();

  // Export to external CRM
  for (const contact of contacts) {
    await fetch('https://external-crm.com/api/contacts/sync', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer crm_token',
      },
      body: JSON.stringify({
        external_id: contact.id,
        name: contact.name,
        phone: contact.phone,
        email: contact.email,
        platform: contact.platform,
        last_activity: contact.last_message_at,
        status: contact.chat_status,
      }),
    });
  }

  console.log(`Exported ${contacts.length} contacts to CRM`);
};
```

### 6. Filter Contacts by Pipeline Stage

```javascript theme={null}
const getContactsByStage = async (days, stageId) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/contacts?days=${days}&stage_id=${stageId}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();
  console.log(`Found ${contacts.length} contacts in stage ${stageId}`);

  return contacts;
};

// Example: Get all leads from the last 30 days
const leadStageId = '123e4567-e89b-12d3-a456-426614174000';
const leads = await getContactsByStage(30, leadStageId);
```

### 7. Activity Dashboard

```javascript theme={null}
const buildActivityDashboard = async () => {
  const periods = [
    { label: 'Today', days: 1 },
    { label: 'Last 7 days', days: 7 },
    { label: 'Last 30 days', days: 30 },
    { label: 'Last 90 days', days: 90 },
  ];

  const dashboard = {};

  for (const period of periods) {
    const response = await fetch(
      `https://api.vambe.me/api/public/contacts?days=${period.days}`,
      {
        headers: {
          'x-api-key': 'your_api_key_here',
        },
      },
    );

    const contacts = await response.json();

    dashboard[period.label] = {
      total: contacts.length,
      unattended: contacts.filter((c) => c.chat_status === 'UNATTENDED').length,
      attended: contacts.filter((c) => c.chat_status === 'ATTENDED').length,
      byChannel: contacts.reduce((acc, c) => {
        acc[c.platform] = (acc[c.platform] || 0) + 1;
        return acc;
      }, {}),
    };
  }

  console.log('Activity Dashboard:', dashboard);
  return dashboard;
};
```

## Query Parameters Details

### Days Parameter

The `days` parameter filters contacts where `last_message_at` is within the specified number of days from now:

| Days Value | Description                 | Use Case                      |
| ---------- | --------------------------- | ----------------------------- |
| `1`        | Last 24 hours               | Today's activity              |
| `7`        | Last week                   | Weekly engagement report      |
| `30`       | Last month                  | Monthly active contacts       |
| `90`       | Last quarter                | Quarterly analysis            |
| `180`      | Last 6 months               | Semi-annual review            |
| `365`      | Last year (maximum allowed) | Annual customer base analysis |

### Stage ID Parameter

The optional `stage_id` parameter filters contacts by their current pipeline stage. Only contacts with an active ticket in the specified stage will be returned.

| Parameter  | Type   | Required | Description                             |
| ---------- | ------ | -------- | --------------------------------------- |
| `stage_id` | string | No       | UUID of the pipeline stage to filter by |

<Note>
  **Stage Filtering**: When `stage_id` is provided, only contacts with an active
  ticket (`active_ticket_v2`) in that specific stage are returned. Contacts
  without an active ticket or in a different stage are excluded.
</Note>

### Pipeline ID Parameter

The optional `pipeline_id` parameter filters contacts by the pipeline of their active ticket's current stage.

| Parameter     | Type   | Required | Description                       |
| ------------- | ------ | -------- | --------------------------------- |
| `pipeline_id` | string | No       | UUID of the pipeline to filter by |

<Note>
  **Pipeline Filtering**: When `pipeline_id` is provided, only contacts with an active
  ticket whose current stage belongs to that pipeline are returned. You can combine
  `pipeline_id` with `stage_id` to filter by both.
</Note>

## Response Ordering

Contacts are returned ordered by `last_message_at` in **descending order** (most recent first).

## Cross-Channel Support

This endpoint returns contacts from **all channels**:

| Platform       | Description               |
| -------------- | ------------------------- |
| `whatsapp`     | WhatsApp Business API     |
| `web-whatsapp` | WhatsApp Web (QR)         |
| `instagram`    | Instagram Direct Messages |
| `webchat`      | Web chat widget           |
| `sms`          | SMS messages              |
| `messenger`    | Facebook Messenger        |
| `tiktok`       | TikTok Direct Messages    |
| `voice`        | Voice calls               |
| `playground`   | Test/playground channel   |

## Error Responses

| Status Code | Description                                          |
| ----------- | ---------------------------------------------------- |
| 400         | Bad Request - Invalid days parameter (must be 1-365) |
| 401         | Unauthorized - Invalid or missing API key            |
| 500         | Internal Server Error - Something went wrong         |

## Important Notes

* **Days Range**: Must be between 1 and 365 days
* **Null Values**: Some fields like `email` or `phone` may be null
* **Performance**: Response time increases with larger `days` values
* **All Channels**: Unlike the deprecated WhatsApp-specific endpoint, this returns contacts from ALL channels
* **Contact Count**: No pagination - returns all contacts matching the criteria
* **Stage Filtering**: When `stage_id` is provided, only contacts with an active ticket in that stage are returned
* **Pipeline Filtering**: When `pipeline_id` is provided, only contacts with an active ticket in a stage belonging to that pipeline are returned
* **Combined Filters**: `days`, `stage_id`, and `pipeline_id` can be used together to narrow down results
* **Active Ticket**: The `active_ticket_v2` object contains the current ticket's `id` and `current_stage_id`

## Performance Considerations

* **Cache Results**: Consider caching responses for frequently used `days` values
* **Large Datasets**: For `days` values > 90, expect larger response sizes
* **Rate Limiting**: Be mindful of rate limits when making frequent requests

## Comparison with Deprecated Endpoint

| Feature            | New Endpoint (This)       | Deprecated Endpoint                     |
| ------------------ | ------------------------- | --------------------------------------- |
| **Path**           | `/api/public/contacts`    | `/api/public/whatsapp/contact/contacts` |
| **Channels**       | All channels              | WhatsApp only                           |
| **Response**       | AI Contact objects        | WhatsApp Contact objects                |
| **Ordering**       | By last\_message\_at DESC | Not specified                           |
| **Future Support** | ✅ Active development      | ❌ Deprecated                            |

## Related Endpoints

* [GET /api/public/contact/{aiContactId}/info](/reference/contact/get-info-of-an-ai-contact) - Get detailed contact info
* [POST /api/public/customer/upsert/info](/reference/customer/upsert-customer-info) - Create or update contact
* [POST /api/public/contact/{aiContactId}/update-metadata](/reference/contact/update-contact-metadata) - Update contact metadata

## Best Practices

### 1. Choose Appropriate Days Range

```javascript theme={null}
// Good: Specific use case
const getWeeklyActive = () => fetch('...?days=7');
const getMonthlyActive = () => fetch('...?days=30');

// Avoid: Unnecessarily large range
const getAll = () => fetch('...?days=365'); // Only if you need a full year
```

### 2. Handle Empty Results

```javascript theme={null}
const contacts = await fetch('...?days=7').then((r) => r.json());

if (contacts.length === 0) {
  console.log('No contacts in the last 7 days');
} else {
  console.log(`Found ${contacts.length} active contacts`);
}
```

### 3. Filter on Client Side

```javascript theme={null}
const contacts = await fetch('...?days=30').then((r) => r.json());

// Filter by platform
const whatsappContacts = contacts.filter((c) => c.platform === 'whatsapp');

// Filter by status
const unread = contacts.filter((c) => !c.is_chat_read);

// Filter by date range
const thisWeek = contacts.filter((c) => {
  const msgDate = new Date(c.last_message_at);
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
  return msgDate > sevenDaysAgo;
});
```


## OpenAPI

````yaml get /api/public/contacts
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/contacts:
    get:
      tags:
        - Contact
      summary: Get contacts by time filters
      description: >-
        Retrieve contacts filtered by activity (last message within N days),
        creation time, or update time. Works across all channels (WhatsApp,
        Instagram, etc.). At least one of days, created_after, or updated_after
        is required. For incremental sync, poll with updated_after set to the
        latest updated_at you have stored: the response includes both created_at
        and updated_at so you can tell new contacts (created_at > your cursor)
        apart from updated ones. Results are ordered ascending by updated_at (or
        created_at) when those filters are used, so you can page forward with
        limit/offset and advance your cursor safely. Optionally filter by custom
        field values.
      operationId: PublicContactsController_getContacts
      parameters:
        - name: days
          required: false
          in: query
          description: Filter contacts whose last message was within this many days (1-365)
          schema:
            minimum: 1
            maximum: 365
            example: 30
            type: number
        - name: created_after
          required: false
          in: query
          description: >-
            Return only contacts created strictly after this ISO 8601 timestamp.
            Use for syncing newly created contacts.
          schema:
            format: date-time
            pattern: >-
              ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
            example: '2024-01-15T10:00:00.000Z'
            type: string
        - name: updated_after
          required: false
          in: query
          description: >-
            Return only contacts updated strictly after this ISO 8601 timestamp.
            Use as a sync cursor to pull both new and modified contacts since
            the last poll.
          schema:
            format: date-time
            pattern: >-
              ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
            example: '2024-01-15T10:00:00.000Z'
            type: string
        - name: limit
          required: false
          in: query
          description: Maximum number of contacts to return (1-1000)
          schema:
            minimum: 1
            maximum: 1000
            example: 100
            type: number
        - name: offset
          required: false
          in: query
          description: Number of contacts to skip, for pagination
          schema:
            minimum: 0
            maximum: 9007199254740991
            example: 0
            type: number
        - name: stage_id
          required: false
          in: query
          description: Stage ID to filter contacts
          schema:
            format: uuid
            pattern: >-
              ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
            example: 123e4567-e89b-12d3-a456-426614174000
            type: string
        - name: pipeline_id
          required: false
          in: query
          description: Pipeline ID to filter contacts by their active ticket stage
          schema:
            format: uuid
            pattern: >-
              ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
            example: 123e4567-e89b-12d3-a456-426614174000
            type: string
        - name: custom_field_key
          required: false
          in: query
          description: >-
            The key of the custom field to filter by. Must be provided together
            with custom_field_value.
          schema:
            minLength: 1
            example: status
            type: string
        - name: custom_field_value
          required: false
          in: query
          description: >-
            The value to match for the custom field. Must be provided together
            with custom_field_key.
          schema:
            minLength: 1
            example: Active
            type: string
        - name: entity_type
          required: false
          in: query
          description: >-
            Specify "ticket" or "customer" to filter only by custom fields of
            that entity type. Only applies when custom_field_key and
            custom_field_value are provided.
          schema:
            enum:
              - ticket
              - customer
            type: string
        - name: x-api-key
          in: header
          description: API key needed to authorize the request
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Contacts retrieved successfully.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      example: df980fc8-b6db-4820-bf22-2969482d106d
                    name:
                      type: string
                      example: John Doe
                    phone:
                      type: string
                      example: '+56912345678'
                    email:
                      type: string
                      example: john@example.com
                    platform:
                      type: string
                      example: whatsapp
                    last_message_at:
                      type: string
                      example: '2024-09-30T10:00:00.000Z'
                    chat_status:
                      type: string
                      example: ATTENDED
                    created_at:
                      type: string
                      example: '2024-01-15T10:00:00.000Z'
                    updated_at:
                      type: string
                      example: '2024-01-16T09:30:00.000Z'
                    client_id:
                      type: string
                      example: 123e4567-e89b-12d3-a456-426614174000
                    ai_customer_id:
                      type: string
                      nullable: true
                      example: 123e4567-e89b-12d3-a456-426614174000
                    default_stage_id:
                      type: string
                      nullable: true
                      example: 123e4567-e89b-12d3-a456-426614174000
                    blocked:
                      type: boolean
                      example: false
                    is_chat_read:
                      type: boolean
                      example: true
                    active_ticket_v2:
                      type: object
                      nullable: true
                      properties:
                        id:
                          type: string
                          example: 123e4567-e89b-12d3-a456-426614174000
                        current_stage_id:
                          type: string
                          nullable: true
                          example: 123e4567-e89b-12d3-a456-426614174000
        '400':
          description: >-
            Bad request - Invalid parameters or custom field not
            found/incompatible.
        '401':
          description: Unauthorized - Invalid or missing API key.

````