GET
/
api
/
public
/
contacts
/
search
Search contacts by phone or email
curl --request GET \
  --url https://api.vambe.me/api/public/contacts/search \
  --header 'x-api-key: <x-api-key>'
[
  {
    "id": "df980fc8-b6db-4820-bf22-2969482d106d",
    "name": "John Doe",
    "phone": "+56912345678",
    "email": "john@example.com",
    "platform": "whatsapp",
    "last_message_at": "2024-09-30T10:00:00.000Z",
    "chat_status": "ATTENDED",
    "created_at": "2024-01-15T10:00:00.000Z"
  }
]

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

phone
string
Phone number to search (partial match supported). Numbers are automatically sanitized.Examples: "+56912345678", "56912345678", "12345678"
email
string
Email address to search (partial match supported, case-insensitive).Examples: "john@example.com", "@example.com", "john"
At least one parameter required: You must provide either phone or email (or both).

Response Structure

Returns an array of matching contact objects (maximum 50 results):
FieldTypeDescription
idstring (UUID)Unique identifier for the contact
namestringContact’s full name
phonestring | nullContact’s phone number
emailstring | nullContact’s email address
platformstringCommunication channel (whatsapp, instagram, etc.)
last_message_atstring (ISO)Timestamp of the last message sent/received
chat_statusstringCurrent chat status (UNATTENDED, ATTENDED, etc.)
created_atstring (ISO)Contact creation timestamp
client_idstring (UUID)Your organization ID
ai_customer_idstring (UUID)Associated customer profile ID
default_stage_idstring (UUID)Current pipeline stage
blockedbooleanWhether the contact is blocked
is_chat_readbooleanWhether the chat has been read by an agent

Example Requests

Search by Phone Number

curl --request GET \
  'https://api.vambe.ai/api/public/contacts/search?phone=%2B56912345678' \
  --header 'x-api-key: your_api_key_here'

Search by Email

curl --request GET \
  'https://api.vambe.ai/api/public/contacts/search?email=john%40example.com' \
  --header 'x-api-key: your_api_key_here'

Search by Both

curl --request GET \
  'https://api.vambe.ai/api/public/contacts/search?phone=%2B56912345678&email=john%40example.com' \
  --header 'x-api-key: your_api_key_here'

Example Response

[
  {
    "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
  }
]

Common Use Cases

1. Check if Contact Exists Before Creating

const checkContactExists = async (phone) => {
  const response = await fetch(
    `https://api.vambe.ai/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

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.ai/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

const verifyAndSendMessage = async (customerPhone) => {
  // First, search for the contact
  const searchResponse = await fetch(
    `https://api.vambe.ai/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

const findContactsByDomain = async (domain) => {
  const response = await fetch(
    `https://api.vambe.ai/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

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.ai/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;
};
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.ai/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.
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 CodeDescription
400Bad Request - Neither phone nor email provided
401Unauthorized - Invalid or missing API key
500Internal 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:
CharacterURL EncodedExample
+%2B+56912345678 → %2B56912345678
@%40john@example.com → john%40example.com
Space%20john doe → john%20doe
Most HTTP libraries handle this automatically with encodeURIComponent().

Best Practices

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.ai/api/public/contacts/search?${params}`,
    {
      headers: { 'x-api-key': 'your_api_key_here' },
    },
  ).then((r) => r.json());
};

2. Handle Multiple Results

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

const findContactWithFallback = async (phone, email) => {
  // Try phone first
  if (phone) {
    const byPhone = await fetch(
      `https://api.vambe.ai/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.ai/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

Headers

x-api-key
string
required

API key needed to authorize the request

Query Parameters

phone
string

Phone number to search (partial match supported)

Example:

"+56912345678"

email
string<email>

Email address to search (partial match supported)

Example:

"john@example.com"

Response

Contacts found successfully.

id
string
Example:

"df980fc8-b6db-4820-bf22-2969482d106d"

name
string
Example:

"John Doe"

phone
string
Example:

"+56912345678"

email
string
Example:

"john@example.com"

platform
string
Example:

"whatsapp"

last_message_at
string
Example:

"2024-09-30T10:00:00.000Z"

chat_status
string
Example:

"ATTENDED"

created_at
string
Example:

"2024-01-15T10:00:00.000Z"