POST
/
api
/
public
/
channels
/
{channelType}
/
{platformIdentifier}
/
tags
Create tags for a contact
curl --request POST \
  --url https://api.vambe.me/api/public/channels/{channelType}/{platformIdentifier}/tags \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <x-api-key>' \
  --data '{
  "tags": [
    "<string>"
  ]
}'
{
  "status": "success",
  "createdCount": 2,
  "assignedCount": 3,
  "notAssignedCount": 1,
  "notAssignedTags": [
    "vip-ticket"
  ],
  "details": "Some tags were not assigned because they are TICKET type and the contact has no active ticket."
}

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

ParameterTypeRequiredDescription
channelTypestringYesChannel type: “whatsapp”, “web-whatsapp”, “instagram”, “playground”
platformIdentifierstringYesPhone number (with country code) or username

Request Body

FieldTypeRequiredDescription
tagsstring[]YesArray of tag names (strings) to create/assign

Response Structure

FieldTypeDescription
statusstringStatus of the operation (always “success”)
createdCountnumberNumber of new tags created
assignedCountnumberNumber of tags successfully assigned to the contact
notAssignedCountnumberNumber of tags that couldn’t be assigned
notAssignedTagsstring[]Names of tags that weren’t assigned
detailsstringExplanation of why some tags weren’t assigned (if any)

Example Request

curl --request POST \
  'https://api.vambe.ai/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

{
  "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:
{
  "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

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

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

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

const bulkTagFromCSV = async (csvData) => {
  const results = [];

  for (const row of csvData) {
    try {
      const result = await fetch(
        `https://api.vambe.ai/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

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

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

  const response = await fetch(
    `https://api.vambe.ai/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 TypePlatform Identifier FormatExample
whatsappPhone with country code+56912345678
web-whatsappPhone with country code+56912345678
instagramUsername (no @)customer_username
playgroundPhone 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:
{
  "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 CodeDescription
400Bad Request - Invalid channel type or missing tags
401Unauthorized - Invalid or missing API key
404Not Found - Contact not found for the specified channel/identifier
500Internal 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

FeaturePOST (This Endpoint)PATCH (Update Tags)
Lookup MethodPhone/usernameContact ID (UUID)
Tag FormatTag names (strings)Tag IDs (numbers)
BehaviorAdds tags (additive)Replaces all tags
Tag CreationAuto-creates missing tagsRequires existing tag IDs
Response DetailCreated/assigned/not-assigned countsSimple success + count

Best Practices

1. Normalize Phone Numbers

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

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

// Good: kebab-case, descriptive
const goodTags = [
  'vip-customer',
  'purchased-electronics',
  'newsletter-subscriber',
];

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

Complete Workflow Example

// 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.ai/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,
    };
  }
};

Headers

x-api-key
string
required

API key for request authorization

Path Parameters

channelType
enum<string>
required

The type of channel to create a tag for

Available options:
whatsapp,
playground,
web-whatsapp,
instagram
platformIdentifier
string
required

The platform identifier for the contact, e.g. phone number (with country code) or username

Body

application/json
tags
string[]

Response

Tags processed successfully

status
string
Example:

"success"

createdCount
number
Example:

2

assignedCount
number
Example:

3

notAssignedCount
number
Example:

1

notAssignedTags
string[]
Example:
["vip-ticket"]
details
string
Example:

"Some tags were not assigned because they are TICKET type and the contact has no active ticket."