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

# Create tags for a contact

> Creates tags for the contact for the specified channel. TICKET-type tags are only assigned when the contact has an active ticket. The response reports counts for created, assigned, and not-assigned tags, plus details explaining why some could not be assigned.

## Overview

Create and assign tags to a contact using their channel information (phone number or username) instead of contact ID. This endpoint is perfect when you know the contact's phone/username but not their Vambe contact ID.

The endpoint automatically creates tags that don't exist and intelligently handles TICKET-type tags (only assigned when the contact has an active ticket).

## Use Cases

* **Webhook Integration**: Tag contacts from external webhooks using phone numbers
* **Form Submissions**: Tag contacts from web forms without looking up their ID
* **CSV Imports**: Bulk tag contacts using phone numbers from spreadsheets
* **E-commerce Events**: Tag customers after purchases using their phone
* **Marketing Automation**: Auto-tag contacts from campaign responses
* **Support Tickets**: Tag customers when tickets are created in 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
```

## Path Parameters

| Parameter            | Type   | Required | Description                                                         |
| -------------------- | ------ | -------- | ------------------------------------------------------------------- |
| `channelType`        | string | Yes      | Channel type: "whatsapp", "web-whatsapp", "instagram", "playground" |
| `platformIdentifier` | string | Yes      | Phone number (with country code) or username                        |

## Request Body

| Field  | Type      | Required | Description                                   |
| ------ | --------- | -------- | --------------------------------------------- |
| `tags` | string\[] | Yes      | Array of tag names (strings) to create/assign |

## Response Structure

| Field              | Type      | Description                                            |
| ------------------ | --------- | ------------------------------------------------------ |
| `status`           | string    | Status of the operation (always "success")             |
| `createdCount`     | number    | Number of new tags created                             |
| `assignedCount`    | number    | Number of tags successfully assigned to the contact    |
| `notAssignedCount` | number    | Number of tags that couldn't be assigned               |
| `notAssignedTags`  | string\[] | Names of tags that weren't assigned                    |
| `details`          | string    | Explanation of why some tags weren't assigned (if any) |

## Example Request

```bash theme={null}
curl --request POST \
  'https://api.vambe.me/api/public/channels/whatsapp/+56912345678/tags' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "tags": ["vip-customer", "interested-in-premium", "contacted-2024"]
  }'
```

## Example Response

```json theme={null}
{
  "status": "success",
  "createdCount": 2,
  "assignedCount": 3,
  "notAssignedCount": 0,
  "notAssignedTags": [],
  "details": null
}
```

## Example Response with Ticket Tags

When a contact has no active ticket and you try to assign TICKET-type tags:

```json theme={null}
{
  "status": "success",
  "createdCount": 1,
  "assignedCount": 2,
  "notAssignedCount": 1,
  "notAssignedTags": ["urgent-ticket"],
  "details": "Some tags were not assigned because they are TICKET type and the contact has no active ticket."
}
```

## Common Use Cases

### 1. Tag Contact from Webhook

```javascript theme={null}
const tagContactFromWebhook = async (phoneNumber, eventTags) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${phoneNumber}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        tags: eventTags,
      }),
    },
  );

  const result = await response.json();
  console.log(`Created ${result.createdCount} new tags`);
  console.log(`Assigned ${result.assignedCount} tags to contact`);

  return result;
};

// Usage from webhook
// tagContactFromWebhook('+56912345678', ['webinar-attendee', 'lead']);
```

### 2. Tag Customer After Purchase

```javascript theme={null}
const tagAfterPurchase = async (customerPhone, productCategory, orderValue) => {
  const tags = ['customer', `purchased-${productCategory}`];

  // Add VIP tag for high-value purchases
  if (orderValue > 500) {
    tags.push('vip-customer', 'high-value-order');
  }

  const response = await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${customerPhone}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        tags: tags,
      }),
    },
  );

  return await response.json();
};

// Usage
tagAfterPurchase('+56912345678', 'electronics', 750);
```

### 3. Tag from Form Submission

```javascript theme={null}
const tagFromContactForm = async (formData) => {
  const phone = formData.phone;
  const tags = ['contact-form-lead', `interest-${formData.interest}`];

  // Add urgency tag if requested
  if (formData.urgency === 'high') {
    tags.push('urgent-contact');
  }

  const response = await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${phone}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        tags: tags,
      }),
    },
  );

  const result = await response.json();

  if (result.notAssignedCount > 0) {
    console.warn('Some tags not assigned:', result.notAssignedTags);
    console.warn('Reason:', result.details);
  }

  return result;
};
```

### 4. Bulk Tag from CSV

```javascript theme={null}
const bulkTagFromCSV = async (csvData) => {
  const results = [];

  for (const row of csvData) {
    try {
      const result = await fetch(
        `https://api.vambe.me/api/public/channels/whatsapp/${row.phone}/tags`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': 'your_api_key_here',
          },
          body: JSON.stringify({
            tags: row.tags.split(','), // CSV might have "tag1,tag2,tag3"
          }),
        },
      );

      const data = await result.json();
      results.push({ phone: row.phone, success: true, ...data });
    } catch (error) {
      results.push({ phone: row.phone, success: false, error: error.message });
    }

    // Add delay to avoid rate limiting
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  console.log(
    `Tagged ${results.filter((r) => r.success).length} of ${csvData.length} contacts`,
  );

  return results;
};
```

### 5. Tag Instagram Contact

```javascript theme={null}
const tagInstagramContact = async (username, tags) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/channels/instagram/${username}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        tags: tags,
      }),
    },
  );

  return await response.json();
};

// Usage
tagInstagramContact('customer_username', ['dm-lead', 'interested-in-product']);
```

### 6. Campaign Response Tagging

```javascript theme={null}
const tagCampaignResponse = async (phoneNumber, campaignName, responseType) => {
  const tags = [
    `campaign-${campaignName}`,
    `response-${responseType}`,
    'engaged-contact',
  ];

  const response = await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${phoneNumber}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        tags: tags,
      }),
    },
  );

  const result = await response.json();

  return {
    ...result,
    campaignName,
    phoneNumber,
  };
};

// Usage
tagCampaignResponse('+56912345678', 'summer-sale-2024', 'clicked-link');
```

## Channel Types

| Channel Type   | Platform Identifier Format | Example             |
| -------------- | -------------------------- | ------------------- |
| `whatsapp`     | Phone with country code    | `+56912345678`      |
| `web-whatsapp` | Phone with country code    | `+56912345678`      |
| `instagram`    | Username (no @)            | `customer_username` |
| `playground`   | Phone or identifier        | `+56912345678`      |

## Platform Identifier Formatting

The endpoint automatically cleans the platform identifier:

* Removes `+` symbols
* Removes spaces
* Normalizes the format

So these are all equivalent:

* `+56912345678`
* `56912345678`
* `+569 1234 5678`

## Tag Creation Behavior

### Automatic Tag Creation

If a tag doesn't exist, it will be **automatically created** for your organization:

**Request**: `{ "tags": ["new-tag-2024"] }`

1. System checks if tag "new-tag-2024" exists
2. If not, creates it
3. Assigns it to the contact
4. Returns `createdCount: 1`

### Tag Assignment

* **CONTACT Tags**: Always assigned to the contact
* **TICKET Tags**: Only assigned if contact has an active ticket

### Response Breakdown

**Example Scenario**:

* Request tags: `["customer", "vip", "urgent-ticket"]`
* "customer" and "vip" exist as CONTACT tags
* "urgent-ticket" exists as a TICKET tag
* Contact has NO active ticket

**Response**:

```json theme={null}
{
  "createdCount": 0,
  "assignedCount": 2,
  "notAssignedCount": 1,
  "notAssignedTags": ["urgent-ticket"],
  "details": "Some tags were not assigned because they are TICKET type and the contact has no active ticket."
}
```

## Error Responses

| Status Code | Description                                                        |
| ----------- | ------------------------------------------------------------------ |
| 400         | Bad Request - Invalid channel type or missing tags                 |
| 401         | Unauthorized - Invalid or missing API key                          |
| 404         | Not Found - Contact not found for the specified channel/identifier |
| 500         | Internal Server Error - Something went wrong                       |

## Important Notes

* **Auto-Create**: Tags are automatically created if they don't exist
* **Additive Operation**: This endpoint **adds** tags, it doesn't replace existing ones
* **Case Sensitive**: Tag names are case-sensitive
* **Duplicates**: Duplicate tags in the array won't create duplicate assignments
* **Contact Lookup**: Contact must exist with the specified platform identifier
* **Phone Format**: Phone numbers should include country code (e.g., +56 for Chile)

## Differences from PATCH Endpoint

| Feature             | POST (This Endpoint)                 | PATCH (Update Tags)       |
| ------------------- | ------------------------------------ | ------------------------- |
| **Lookup Method**   | Phone/username                       | Contact ID (UUID)         |
| **Tag Format**      | Tag names (strings)                  | Tag IDs (numbers)         |
| **Behavior**        | Adds tags (additive)                 | Replaces all tags         |
| **Tag Creation**    | Auto-creates missing tags            | Requires existing tag IDs |
| **Response Detail** | Created/assigned/not-assigned counts | Simple success + count    |

## Best Practices

### 1. Normalize Phone Numbers

```javascript theme={null}
const normalizePhone = (phone) => {
  // Remove all non-numeric characters except +
  return phone.replace(/[^\d+]/g, '');
};

const tagByPhone = async (rawPhone, tags) => {
  const normalizedPhone = normalizePhone(rawPhone);

  return await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${normalizedPhone}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({ tags }),
    },
  ).then((r) => r.json());
};
```

### 2. Handle Ticket Tags Gracefully

```javascript theme={null}
const tagWithTicketCheck = async (phone, tags) => {
  const response = await fetch(
    `https://api.vambe.me/api/public/channels/whatsapp/${phone}/tags`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({ tags }),
    },
  );

  const result = await response.json();

  if (result.notAssignedCount > 0) {
    console.log(`Warning: ${result.notAssignedCount} tags not assigned`);
    console.log('Reason:', result.details);
    console.log('Tags:', result.notAssignedTags);
  }

  return result;
};
```

### 3. Use Consistent Tag Naming

```javascript theme={null}
// Good: kebab-case, descriptive
const goodTags = [
  'vip-customer',
  'purchased-electronics',
  'newsletter-subscriber',
];

// Avoid: mixed case, spaces, unclear
const badTags = ['VIP Customer', 'bought stuff', 'tag1'];
```

## Related Endpoints

* [GET /api/public/tags](/reference/tags/get-all-tags) - Get all available tags and their IDs
* [PATCH /api/public/contact/tags/{aiContactId}](/reference/tags/update-contact-tags) - Update tags using contact ID
* [GET /api/public/contact/{aiContactId}/info](/reference/contact/get-info-of-an-ai-contact) - Get contact info including tags
* [POST /api/public/ticket/open/web-whatsapp/{phoneId}](/reference/ticket/open-ticket-web-whatsapp) - Create ticket (enables TICKET tags)

## Complete Workflow Example

```javascript theme={null}
// Complete workflow: Form submission -> Contact tagging
const handleFormSubmission = async (formData) => {
  const { name, phone, email, interest, source } = formData;

  // 1. Normalize phone
  const cleanPhone = phone.replace(/[^\d+]/g, '');

  // 2. Determine tags based on form data
  const tags = [
    'contact-form-lead',
    `source-${source}`,
    `interest-${interest}`,
    `submitted-${new Date().toISOString().split('T')[0]}`,
  ];

  // 3. Add urgency tag if email includes 'urgent'
  if (email.includes('urgent')) {
    tags.push('urgent-contact');
  }

  // 4. Tag the contact
  try {
    const result = await fetch(
      `https://api.vambe.me/api/public/channels/whatsapp/${cleanPhone}/tags`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.VAMBE_API_KEY,
        },
        body: JSON.stringify({ tags }),
      },
    );

    const data = await result.json();

    console.log(`✅ Tagged contact ${name}`);
    console.log(`   Created: ${data.createdCount} tags`);
    console.log(`   Assigned: ${data.assignedCount} tags`);

    return {
      success: true,
      ...data,
    };
  } catch (error) {
    console.error('Failed to tag contact:', error);
    return {
      success: false,
      error: error.message,
    };
  }
};
```


## OpenAPI

````yaml post /api/public/channels/{channelType}/{platformIdentifier}/tags
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/channels/{channelType}/{platformIdentifier}/tags:
    post:
      tags:
        - Channels
      summary: Create tags for a contact
      description: >-
        Creates tags for the contact for the specified channel. TICKET-type tags
        are only assigned when the contact has an active ticket. The response
        reports counts for created, assigned, and not-assigned tags, plus
        details explaining why some could not be assigned.
      operationId: PublicChannelController_addTag
      parameters:
        - name: platformIdentifier
          required: true
          in: path
          description: >-
            The platform identifier for the contact, e.g. phone number (with
            country code) or username
          schema:
            type: string
        - name: x-api-key
          in: header
          description: API key for request authorization
          required: true
          schema:
            type: string
        - name: channelType
          required: true
          in: path
          description: The type of channel to create a tag for
          schema:
            enum:
              - whatsapp
              - playground
              - web-whatsapp
              - instagram
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tags:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: Tags processed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: success
                  createdCount:
                    type: number
                    example: 2
                  assignedCount:
                    type: number
                    example: 3
                  notAssignedCount:
                    type: number
                    example: 1
                  notAssignedTags:
                    type: array
                    items:
                      type: string
                    example:
                      - vip-ticket
                  details:
                    type: string
                    example: >-
                      Some tags were not assigned because they are TICKET type
                      and the contact has no active ticket.
        '401':
          description: Unauthorized - Invalid or missing API key
        '404':
          description: >-
            Not Found - No contact found for the specified channel type and
            platform identifier

````