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

# Search contacts by phone or email

> Find contacts by phone number or email address. Searches across all channels. At least one search parameter is required.

## Overview

Search for contacts using their phone number or email address. This endpoint performs fuzzy matching across all communication channels, making it easy to find contacts even with partial information.

Perfect for implementing search functionality, verifying contact existence, or looking up customer information before taking action.

## Use Cases

* **Contact Lookup**: Find a contact before creating a ticket or sending a message
* **Duplicate Detection**: Check if a contact already exists before importing
* **Search Interface**: Power search bars in your CRM or dashboard
* **Customer Verification**: Verify customer identity during support interactions
* **Data Enrichment**: Find existing contact data to enrich external records
* **Phone Validation**: Check if a phone number is already in your system

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

<ParamField query="phone" type="string">
  Phone number to search (partial match supported). Numbers are automatically sanitized.

  Examples: `"+56912345678"`, `"56912345678"`, `"12345678"`
</ParamField>

<ParamField query="email" type="string">
  Email address to search (partial match supported, case-insensitive).

  Examples: `"john@example.com"`, `"@example.com"`, `"john"`
</ParamField>

<Note>
  **At least one parameter required**: You must provide either `phone` or
  `email` (or both).
</Note>

## Response Structure

Returns an array of matching contact objects (maximum 50 results):

| 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)  | Current pipeline stage                                    |
| `blocked`          | boolean        | Whether the contact is blocked                            |
| `is_chat_read`     | boolean        | Whether the chat has been read by an agent                |
| `utm_events`       | array          | Marketing attribution events for this contact (see below) |

### UTM Event Object (`utm_events[]`)

Marketing attribution events recorded for the contact, ordered oldest first. Empty array when the contact has no attribution events.

| Field              | Type                  | Description                                      |
| ------------------ | --------------------- | ------------------------------------------------ |
| `id`               | string (UUID)         | Unique identifier for the UTM event              |
| `created_at`       | string (ISO) \| null  | When the attribution event was recorded          |
| `customer_id`      | string (UUID) \| null | Associated customer profile ID                   |
| `integration_type` | string \| null        | Ad platform (facebook, google, tiktok, etc.)     |
| `ad_id`            | string \| null        | Ad identifier                                    |
| `referrer_url`     | string \| null        | Landing / referrer URL that captured the contact |
| `search_keyword`   | string \| null        | Search keyword that triggered the ad             |

## Example Requests

### Search by Phone Number

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts/search?phone=%2B56912345678' \
  --header 'x-api-key: your_api_key_here'
```

### Search by Email

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts/search?email=john%40example.com' \
  --header 'x-api-key: your_api_key_here'
```

### Search by Both

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/public/contacts/search?phone=%2B56912345678&email=john%40example.com' \
  --header 'x-api-key: your_api_key_here'
```

## Example Response

```json theme={null}
[
  {
    "id": "df980fc8-b6db-4820-bf22-2969482d106d",
    "name": "John Doe",
    "phone": "+56912345678",
    "email": "john.doe@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,
    "utm_events": [
      {
        "id": "ee0e8400-e29b-41d4-a716-446655440009",
        "created_at": "2024-01-14T22:05:11.000Z",
        "customer_id": "660e8400-e29b-41d4-a716-446655440001",
        "integration_type": "facebook",
        "ad_id": "23854715623450123",
        "referrer_url": "https://example.com/landing?utm_campaign=spring",
        "search_keyword": null
      }
    ]
  }
]
```

## Common Use Cases

### 1. Check if Contact Exists Before Creating

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

  const contacts = await response.json();

  if (contacts.length > 0) {
    console.log('Contact already exists:', contacts[0]);
    return contacts[0];
  } else {
    console.log('Contact not found - can create new');
    return null;
  }
};
```

### 2. Search Bar Implementation

```javascript theme={null}
const searchContacts = async (searchTerm) => {
  // Detect if search term is email or phone
  const isEmail = searchTerm.includes('@');
  const queryParam = isEmail
    ? `email=${encodeURIComponent(searchTerm)}`
    : `phone=${encodeURIComponent(searchTerm)}`;

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

  const results = await response.json();
  console.log(`Found ${results.length} matching contacts`);

  return results;
};
```

### 3. Verify Customer Before Action

```javascript theme={null}
const verifyAndSendMessage = async (customerPhone) => {
  // First, search for the contact
  const searchResponse = await fetch(
    `https://api.vambe.me/api/public/contacts/search?phone=${encodeURIComponent(customerPhone)}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await searchResponse.json();

  if (contacts.length === 0) {
    throw new Error('Contact not found. Please create contact first.');
  }

  const contact = contacts[0];

  // Verify not blocked
  if (contact.blocked) {
    throw new Error('Contact is blocked. Cannot send message.');
  }

  console.log(`Found contact: ${contact.name} (${contact.id})`);

  // Now safe to send message or perform action
  return contact;
};
```

### 4. Find All Contacts from Same Domain

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

  const contacts = await response.json();

  console.log(`Found ${contacts.length} contacts from ${domain}`);

  return contacts;
};

// Usage
findContactsByDomain('example.com'); // Finds all @example.com contacts
```

### 5. Deduplicate Before Import

```javascript theme={null}
const importWithDedupe = async (customersToImport) => {
  const results = { created: 0, skipped: 0, updated: 0 };

  for (const customer of customersToImport) {
    // Check if exists
    const searchResponse = await fetch(
      `https://api.vambe.me/api/public/contacts/search?phone=${encodeURIComponent(customer.phone)}`,
      {
        headers: {
          'x-api-key': 'your_api_key_here',
        },
      },
    );

    const existing = await searchResponse.json();

    if (existing.length > 0) {
      console.log(`Skipping ${customer.phone} - already exists`);
      results.skipped++;
    } else {
      // Create new contact
      await createContact(customer);
      results.created++;
    }
  }

  console.log('Import results:', results);
  return results;
};
```

### 6. Multi-criteria Search

```javascript theme={null}
const searchByPhoneOrEmail = async (phone, email) => {
  const params = new URLSearchParams();

  if (phone) params.append('phone', phone);
  if (email) params.append('email', email);

  const response = await fetch(
    `https://api.vambe.me/api/public/contacts/search?${params.toString()}`,
    {
      headers: {
        'x-api-key': 'your_api_key_here',
      },
    },
  );

  const contacts = await response.json();

  // Contacts matching either phone OR email
  console.log(`Found ${contacts.length} contacts`);

  return contacts;
};
```

## Search Behavior

### Phone Number Matching

* **Automatic Sanitization**: Non-numeric characters are removed (`+`, `-`, spaces, etc.)
* **Partial Match**: Finds contacts where phone contains the search term
* **Flexible Format**: Works with or without country code

**Examples**:

* Search `"12345678"` finds `"+56912345678"`
* Search `"+569"` finds all numbers with that prefix
* Search `"56912345"` finds `"+56912345678"`

### Email Matching

* **Case Insensitive**: `"JOHN@EXAMPLE.COM"` matches `"john@example.com"`
* **Partial Match**: Finds contacts where email contains the search term
* **Domain Search**: Search `"@company.com"` finds all contacts from that domain

**Examples**:

* Search `"john@example.com"` finds exact match
* Search `"@example.com"` finds all contacts from that domain
* Search `"john"` finds `"john.doe@anywhere.com"`, `"johnny@test.com"`, etc.

### Combined Search

When both `phone` and `email` are provided, returns contacts matching **either** criteria (OR logic):

```
phone="+56912345678" AND email="john@example.com"
→ Returns contacts with THAT phone OR THAT email
```

## Result Limit

* **Maximum 50 results**: The endpoint returns up to 50 matching contacts
* **Ordered by recent activity**: Results sorted by `last_message_at` descending
* **Refine search**: If you get 50 results, narrow your search criteria

## Error Responses

| Status Code | Description                                    |
| ----------- | ---------------------------------------------- |
| 400         | Bad Request - Neither phone nor email provided |
| 401         | Unauthorized - Invalid or missing API key      |
| 500         | Internal Server Error - Something went wrong   |

## Important Notes

* **At Least One Required**: Must provide `phone`, `email`, or both
* **Partial Matching**: Both phone and email support partial/fuzzy matching
* **Cross-Channel**: Searches across ALL channels (WhatsApp, Instagram, SMS, etc.)
* **Case Insensitive**: Email search is case-insensitive
* **URL Encoding**: Remember to URL-encode parameters (especially `+` in phone numbers and `@` in emails)
* **50 Result Limit**: Only first 50 matches returned

## URL Encoding

When using phone numbers with `+` or emails with `@`, make sure to URL-encode:

| Character | URL Encoded | Example                                   |
| --------- | ----------- | ----------------------------------------- |
| `+`       | `%2B`       | `+56912345678` → `%2B56912345678`         |
| `@`       | `%40`       | `john@example.com` → `john%40example.com` |
| Space     | `%20`       | `john doe` → `john%20doe`                 |

Most HTTP libraries handle this automatically with `encodeURIComponent()`.

## Related Endpoints

* [GET /api/public/contact/{aiContactId}/info](/reference/contact/get-info-of-an-ai-contact) - Get detailed contact info by ID
* [POST /api/public/customer/upsert/info](/reference/customer/upsert-customer-info) - Create or update contact
* [GET /api/public/contacts?days={days}](/reference/contact/get-contacts-by-days) - Get contacts by activity

## Best Practices

### 1. Normalize Input Before Search

```javascript theme={null}
const normalizePhone = (phone) => {
  return phone.replace(/[^\d+]/g, '');
};

const normalizeEmail = (email) => {
  return email.trim().toLowerCase();
};

const searchContact = async (phone, email) => {
  const params = new URLSearchParams();

  if (phone) {
    params.append('phone', normalizePhone(phone));
  }

  if (email) {
    params.append('email', normalizeEmail(email));
  }

  return await fetch(
    `https://api.vambe.me/api/public/contacts/search?${params}`,
    {
      headers: { 'x-api-key': 'your_api_key_here' },
    },
  ).then((r) => r.json());
};
```

### 2. Handle Multiple Results

```javascript theme={null}
const findExactMatch = async (phone, email) => {
  const results = await searchContact(phone, email);

  if (results.length === 0) {
    return null;
  }

  if (results.length === 1) {
    return results[0];
  }

  // Multiple matches - find exact match
  const exactMatch = results.find(
    (c) => c.phone === phone && c.email === email,
  );

  return exactMatch || results[0]; // Return exact or first result
};
```

### 3. Search with Fallback

```javascript theme={null}
const findContactWithFallback = async (phone, email) => {
  // Try phone first
  if (phone) {
    const byPhone = await fetch(
      `https://api.vambe.me/api/public/contacts/search?phone=${encodeURIComponent(phone)}`,
      {
        headers: { 'x-api-key': 'your_api_key_here' },
      },
    ).then((r) => r.json());

    if (byPhone.length > 0) {
      console.log('Found by phone');
      return byPhone[0];
    }
  }

  // Fallback to email
  if (email) {
    const byEmail = await fetch(
      `https://api.vambe.me/api/public/contacts/search?email=${encodeURIComponent(email)}`,
      {
        headers: { 'x-api-key': 'your_api_key_here' },
      },
    ).then((r) => r.json());

    if (byEmail.length > 0) {
      console.log('Found by email');
      return byEmail[0];
    }
  }

  console.log('Contact not found');
  return null;
};
```

## Performance Tips

* **Be Specific**: More specific searches return faster results
* **Exact Match**: Use full phone/email when possible
* **Avoid Wildcards**: Searching `"@"` alone will match too many results
* **Cache Results**: Cache frequent searches to reduce API calls

## Notes

* **Fuzzy Matching**: Uses `ILIKE` for flexible matching
* **No Wildcards Needed**: Partial matching built-in
* **Ordered Results**: Most recent activity first
* **All Channels**: Returns contacts from any platform


## OpenAPI

````yaml get /api/public/contacts/search
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/search:
    get:
      tags:
        - Contact
      summary: Search contacts by phone or email
      description: >-
        Find contacts by phone number or email address. Searches across all
        channels. At least one search parameter is required.
      operationId: PublicContactsController_searchContacts
      parameters:
        - name: phone
          required: false
          in: query
          description: Phone number to search (partial match supported)
          schema:
            type: string
            example: '+56912345678'
        - name: email
          required: false
          in: query
          description: Email address to search (partial match supported)
          schema:
            format: email
            pattern: >-
              ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
            example: john@example.com
            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 found 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
                    utm_events:
                      type: array
                      description: >-
                        Marketing attribution events recorded for this contact,
                        oldest first.
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                          created_at:
                            type: string
                            nullable: true
                            example: '2024-01-15T10:00:00.000Z'
                          customer_id:
                            type: string
                            nullable: true
                          integration_type:
                            type: string
                            nullable: true
                            example: facebook
                          ad_id:
                            type: string
                            nullable: true
                          referrer_url:
                            type: string
                            nullable: true
                            example: https://example.com/landing
                          search_keyword:
                            type: string
                            nullable: true
        '400':
          description: Bad request - Phone or email required.
        '401':
          description: Unauthorized - Invalid or missing API key.

````