# Contacts

> Manage contacts and recipient information

Contacts are automatically created when you send messages. Each contact represents a unique recipient (phone number or email) and stores metadata about their messaging preferences and history.

## How Contacts Work

When you send a message to a new recipient, Zavu automatically:

1. Creates a contact record
2. Detects the country from the phone number
3. Determines available messaging channels
4. Captures the WhatsApp profile name (if available)

```typescript
// Sending to a new recipient creates a contact
const message = await zavu.messages.send({
  to: "+14155551234",
  text: "Welcome!",
});

// Contact is now available
const contact = await zavu.contacts.retrieveByPhone("+14155551234");
console.log(contact.id); // "con_abc123"
```

## Contact Properties

| Property | Description |
|----------|-------------|
| `id` | Unique contact identifier |
| `phoneNumber` | E.164 formatted phone number |
| `countryCode` | Two-letter country code (e.g., "US") |
| `profileName` | WhatsApp display name (if available) |
| `availableChannels` | Channels this contact can receive (sms, whatsapp, etc.) |
| `defaultChannel` | Preferred channel for auto-routing |
| `verified` | Whether the contact has been verified |
| `metadata` | Custom key-value data |

## Get Contact by ID

```typescript
import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev();

const contact = await zavu.contacts.retrieve("con_abc123");

console.log(`Phone: ${contact.phoneNumber}`);
console.log(`Country: ${contact.countryCode}`);
console.log(`Channels: ${contact.availableChannels.join(", ")}`);
console.log(`Profile: ${contact.profileName || "Unknown"}`);
```

```bash
curl https://api.zavu.dev/v1/contacts/{contactId} \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Response

```json
{
  "id": "con_abc123",
  "phoneNumber": "+14155551234",
  "countryCode": "US",
  "profileName": "John Doe",
  "availableChannels": ["sms", "whatsapp"],
  "defaultChannel": "whatsapp",
  "verified": true,
  "metadata": {
    "tier": "premium",
    "source": "website"
  },
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T12:00:00.000Z"
}
```

## Get Contact by Phone Number

Look up a contact using their phone number:

```typescript
const contact = await zavu.contacts.retrieveByPhone("+14155551234");

if (contact) {
  console.log(`Found contact: ${contact.id}`);
  console.log(`Available channels: ${contact.availableChannels}`);
} else {
  console.log("Contact not found");
}
```

```bash
# URL-encode the phone number (+14155551234 becomes %2B14155551234)
curl https://api.zavu.dev/v1/contacts/phone/%2B14155551234 \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

> **Tip:**
> Use this endpoint to check if a recipient already exists before sending messages, or to retrieve their metadata for personalization.

## List Contacts

Retrieve all contacts with pagination:

```typescript
// List all contacts
const { items, nextCursor } = await zavu.contacts.list({
  limit: 50,
});

for (const contact of items) {
  console.log(`${contact.id}: ${contact.phoneNumber}`);
}

// Paginate through all contacts
let cursor: string | undefined;
const allContacts = [];

do {
  const result = await zavu.contacts.list({ cursor, limit: 100 });
  allContacts.push(...result.items);
  cursor = result.nextCursor ?? undefined;
} while (cursor);

console.log(`Total contacts: ${allContacts.length}`);
```

```bash
curl "https://api.zavu.dev/v1/contacts?limit=50" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Filter by Phone Number

`phoneNumber` is an **exact** match on the contact's primary phone number, in
E.164. It is not a prefix filter — use `search` for partial matches.

```typescript
const { items } = await zavu.contacts.list({
  phoneNumber: "+14155551234",
});
```

### Search

`search` matches the contact's name — both `displayName` and the WhatsApp profile
name — as well as its phone numbers and email addresses. It is case- and
accent-insensitive, so `jose` finds "José".

```typescript
const { items } = await zavu.contacts.list({ search: "marta", limit: 25 });
```

A phone number also matches on a trailing fragment, which is how people usually
remember them:

```typescript
// finds +14155551234
const { items } = await zavu.contacts.list({ search: "5551234" });
```

> **Note:**
> Contacts created automatically from an inbound message have no `displayName`, so
> they only match on their identifier until you name one with
> [Update Contact](#update-contact).

> **Warning:**
> Search results come back in relevance order, not newest-first. `cursor` is opaque
> in both modes — pass back exactly what the previous response returned, and start
> a fresh pagination run whenever the search term changes.

### Filter by Tag

Tags group contacts into an audience you send to more than once. Filter by name:

```typescript
const { items } = await zavu.contacts.list({ tag: "vip" });
```

Repeat the parameter to require **every** tag, not any of them:

```bash
curl "https://api.zavu.dev/v1/contacts?tag=vip&tag=chile" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

> **Warning:**
> An unknown tag returns `400`. A typo that silently matched every contact would
> be a worse answer than an error.

> **Note:**
> Tags are created, renamed and assigned from the dashboard. There is no tag CRUD
> in the API yet.

## Update Contact

Update a contact's default channel or metadata:

```typescript
const contact = await zavu.contacts.update("con_abc123", {
  defaultChannel: "whatsapp",
  metadata: {
    name: "John Doe",
    tier: "premium",
    source: "mobile_app",
  },
});

console.log(`Updated default channel: ${contact.defaultChannel}`);
```

```bash
curl -X PATCH https://api.zavu.dev/v1/contacts/{contactId} \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "defaultChannel": "whatsapp",
    "metadata": {
      "name": "John Doe",
      "tier": "premium"
    }
  }'
```

### Updateable Fields

| Field | Description |
|-------|-------------|
| `displayName` | The contact's name. Pass `null` to clear it and fall back to the identifier. |
| `defaultChannel` | Preferred channel for smart routing (sms, whatsapp, telegram, email) |
| `metadata` | Custom key-value pairs (merged with existing metadata) |

> **Note:**
> To change a contact's phone number or email address, add or remove a channel —
> identifiers are not updated through this endpoint.

> **Note:**
> Setting `defaultChannel` affects [smart routing](https://www.zavu.dev/docs/guides/sending-messages/smart-routing). When you send a message with `channel: "auto"`, Zavu will prefer the contact's default channel.

## Contact Metadata

Store custom data on contacts for personalization and segmentation:

```typescript
// Add customer data
await zavu.contacts.update("con_abc123", {
  metadata: {
    customerId: "cust_12345",
    plan: "enterprise",
    signupDate: "2024-01-15",
    preferredLanguage: "es",
  },
});

// Use in messages
const contact = await zavu.contacts.retrieve("con_abc123");
const message = await zavu.messages.send({
  to: contact.phoneNumber,
  text: `Hola ${contact.metadata?.customerId}! Your ${contact.metadata?.plan} plan is active.`,
});
```

> **Info:**
> Metadata values must be strings. Store complex data as JSON strings if needed.

## Available Channels

The `availableChannels` array indicates which channels can reach this contact:

| Channel | When Available |
|---------|----------------|
| `sms` | Valid mobile or landline number |
| `whatsapp` | Contact has WhatsApp (detected on first message or reply) |
| `telegram` | Contact has connected via Telegram bot |
| `email` | Contact has email address on file |

```typescript
const contact = await zavu.contacts.retrieve("con_abc123");

if (contact.availableChannels.includes("whatsapp")) {
  // Send via WhatsApp for richer experience
  await zavu.messages.send({
    to: contact.phoneNumber,
    channel: "whatsapp",
    text: "Hello via WhatsApp!",
  });
} else {
  // Fall back to SMS
  await zavu.messages.send({
    to: contact.phoneNumber,
    channel: "sms",
    text: "Hello via SMS!",
  });
}
```

## Phone Number Introspection

Validate a phone number and check available channels without creating a contact:

```typescript
const result = await zavu.introspect.validatePhone({
  phoneNumber: "+14155551234",
});

console.log(`Valid: ${result.validNumber}`);
console.log(`Country: ${result.countryCode}`);
console.log(`Format: ${result.nationalFormat}`);
console.log(`Type: ${result.lineType}`);
console.log(`Carrier: ${result.carrier?.name}`);
console.log(`Channels: ${result.availableChannels}`);
```

```bash
curl -X POST https://api.zavu.dev/v1/introspect/phone \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+14155551234"
  }'
```

### Response

```json
{
  "phoneNumber": "+14155551234",
  "countryCode": "US",
  "validNumber": true,
  "nationalFormat": "(415) 555-1234",
  "lineType": "mobile",
  "carrier": {
    "name": "Verizon Wireless",
    "type": "mobile"
  },
  "availableChannels": ["sms", "whatsapp"]
}
```

> **Tip:**
> Use introspection to validate phone numbers before adding them to broadcasts or CRM systems.

## Complete Example

```typescript
import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev();

async function processNewCustomer(phone: string, customerData: {
  name: string;
  email: string;
  plan: string;
}) {
  // 1. Validate the phone number
  const introspection = await zavu.introspect.validatePhone({ phoneNumber: phone });

  if (!introspection.validNumber) {
    throw new Error("Invalid phone number");
  }

  console.log(`Valid ${introspection.lineType} number from ${introspection.countryCode}`);

  // 2. Send welcome message (creates contact automatically)
  const message = await zavu.messages.send({
    to: phone,
    channel: "auto", // Smart routing based on available channels
    text: `Welcome ${customerData.name}! Your ${customerData.plan} account is ready.`,
  });

  console.log(`Welcome message sent: ${message.id}`);

  // 3. Update contact with customer data
  const contact = await zavu.contacts.retrieveByPhone(phone);

  await zavu.contacts.update(contact.id, {
    defaultChannel: introspection.availableChannels.includes("whatsapp")
      ? "whatsapp"
      : "sms",
    metadata: {
      name: customerData.name,
      email: customerData.email,
      plan: customerData.plan,
      signupDate: new Date().toISOString(),
    },
  });

  console.log(`Contact updated: ${contact.id}`);

  return contact;
}

// Usage
processNewCustomer("+14155551234", {
  name: "John Doe",
  email: "john@example.com",
  plan: "premium",
});
```

## Next Steps

  - [Smart Routing](https://www.zavu.dev/docs/guides/sending-messages/smart-routing): Learn how defaultChannel affects message routing
  - [Broadcasts](https://www.zavu.dev/docs/guides/broadcasts/overview): Send messages to many contacts at once
  - [Phone Lookup](https://www.zavu.dev/docs/tools/number-lookup): Validate and lookup phone numbers
  - [Webhooks](https://www.zavu.dev/docs/guides/receiving-messages/webhooks): Receive updates when contacts reply

## API Reference

Manage contacts programmatically:

- [List contacts](https://www.zavu.dev/docs/api-reference/list-contacts) — paginated list, with `search` over name, phone and email
- [Get a contact](https://www.zavu.dev/docs/api-reference/get-contact) — retrieve a contact by ID
- [Get contact by phone](https://www.zavu.dev/docs/api-reference/get-contact-by-phone-number) — lookup a contact by phone number
- [Update a contact](https://www.zavu.dev/docs/api-reference/update-contact) — set the name, default channel, or metadata
- [Delete a contact](https://www.zavu.dev/docs/api-reference/delete-contact) — permanently remove a contact
