Contact tags
Manage the CRM's tag catalog, add and remove tags on one contact or hundreds, and filter your base by them.
Tags are the fastest way to segment the CRM: "cliente", "lead-caliente", "Participante". In Met they live in two places, and it pays to keep them apart:
- The catalog (
met.contactTags): the tags that exist in the workspace, even if no contact has them yet. It is what you see in Settings and in the pickers of the panel, broadcasts and flows. - The assignment (
met.contacts.addTagand friends): which tags each contact has. It is stored in the contact's$tagssystem field.
Reading requires contacts:read; writing, contacts:write. Both work with a test key. Routes are flat (/contact-tags, /contacts/…): they operate on the key's workspace.
The catalog
const tags = await met.contactTags.list();
// [{ id: 74, name: 'lead-caliente' }, { id: 75, name: 'Participante' }]
const vip = await met.contactTags.create({ name: 'VIP' });
await met.contactTags.rename(vip.id, 'Cliente VIP');
await met.contactTags.delete(vip.id); // { ok: true }tags = met.contact_tags.list()
vip = met.contact_tags.create({"name": "VIP"})
met.contact_tags.rename(vip["id"], "Cliente VIP")
met.contact_tags.delete(vip["id"])curl -X POST https://api.met.meteor.com.co/api/v1/contact-tags \
-H "Authorization: Bearer $METEOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"VIP"}'list()returns the whole catalog, sorted by name, including tags nobody uses.- The name is unique in the workspace regardless of case:
VIPandvipare the same tag, and creating the second one responds409. Up to 60 characters; stored without surrounding spaces. create()only registers it in the catalog: it does not assign it to any contact.rename()rewrites the name on every contact that had it, so no orphan tags are left behind.delete()removes it from the catalog and from every contact. It cannot be undone.
Adding and removing a contact's tags
await met.contacts.addTag(4821, 'lead-caliente');
await met.contacts.removeTag(4821, 'frío');
const theirs = await met.contacts.tags(4821); // ['lead-caliente', 'Participante']met.contacts.add_tag(4821, "lead-caliente")
met.contacts.remove_tag(4821, "frío")
theirs = met.contacts.tags(4821)Adding a tag to a contact that is not in the catalog registers it in the catalog on its own. Adding one the contact already has does nothing.
Many contacts at once
One tag on a list of contacts, in a single call (up to 500 per call):
const r = await met.contacts.bulkAddTags({ contact_ids: [4821, 4822, 4830], tag: 'Feria 2026' });
// { updated: 2, requested: 3, tag: 'Feria 2026' }
await met.contacts.bulkRemoveTags({ contact_ids: [4821, 4822], tag: 'Feria 2026' });r = met.contacts.bulk_add_tags({"contact_ids": [4821, 4822, 4830], "tag": "Feria 2026"})
met.contacts.bulk_remove_tags({"contact_ids": [4821, 4822], "tag": "Feria 2026"})updated only counts the contacts that actually changed: those that already had the tag, or ids that are not contacts of this workspace, do not add up. It is one tag per call (tag, not a list).
Filtering by tag
contacts.list() accepts conditions on the contact's fields in filters, a JSON array. For tags, the field is $tags and the operators are has and not_has:
const { data, total } = await met.contacts.list({
filters: JSON.stringify([{ field: '$tags', op: 'has', value: 'lead-caliente' }]),
});import json
page = met.contacts.list(filters=json.dumps([{"field": "$tags", "op": "has", "value": "lead-caliente"}]))A malformed condition is ignored instead of raising an error: check total before trusting the filter. To know which tags exist, adding up the catalog's and those contacts have even if they are not registered, use met.contacts.tagsDistinct().
Hearing about changes
There is no tag event of its own. Adding or removing a tag on a contact changes the contact, and that goes out as contact.updated (scope contacts:read; see Webhooks).
Common errors
| Code | What happened |
|---|---|
400 validation_error | The name is empty or longer than 60 characters; in bulk, more than 500 contacts or an empty list. |
404 not_found | The tag (in rename or delete) or the contact does not exist in this workspace. |
409 conflict | A tag with that name already exists, ignoring case. |
403 missing_scope | The key does not carry contacts:write (or contacts:read for reading). |