Etiquetas
El vocabulario suelto que se cuelga de una conversación o de un contacto: adjuntar por nombre lo crea la primera vez.
Descarga la proyección completa de la API pública: openapi.json.
Responder hilos y resolver tickets explica este recurso en prosa, con ejemplos ejecutables.
| Método | Ruta | Qué hace |
|---|---|---|
GET | /tags | List tags |
POST | /tags | Create a tag, or get the existing one |
DELETE | /tags/{id} | Delete a tag |
GET | /tags/{id} | Fetch one tag |
PUT | /tags/{id} | Rename a tag’s display name |
GET | /tags/suggest | Ask the model to suggest tags for a conversation |
GET /tags
List tags
q filters by name. limit defaults to 50 and caps at 200 — there is no cursor, so a workspace with more tags than the limit simply cannot page past it; narrow with q instead.
with_contact_counts=true adds how many contacts carry each tag. It is an aggregate over the join, so leave it off for a picker and turn it on for a management screen.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
q | query | string | no | mín. 1, máx. 120 |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 50 |
with_contact_counts | query | any | no |
curl https://api.vitrinadev.com/api/v1/tags \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "12121212-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "garantia",
"display_name": "Garantía",
"usage_count": 1,
"created_at": "2026-09-22T11:06:38.372Z"
},
{
"id": "12121212-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "urgente",
"display_name": "Urgente",
"usage_count": 4,
"created_at": "2026-09-22T11:06:38.372Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tags
Create a tag, or get the existing one
Idempotent by slug, and the status code is how you tell which happened: 201 when the tag was created, 200 when one with that slug already existed and is being returned instead. Neither is an error, so a UI can submit a freshly typed name without checking first.
name is slugified to form the identity; display_name is what people see and defaults to name as typed. Submitting an existing slug with a different display_name returns the STORED row unchanged — this endpoint will not rename an existing tag, PUT /tags/\{id\} does that.
A name with no alphanumeric characters slugifies to nothing and is a 400. Concurrent creates of the same new name are safe: the loser of the race gets the winner’s row rather than an error.
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
name | string | sí | mín. 1, máx. 120 |
display_name | string | — | mín. 1, máx. 120 |
curl -X POST https://api.vitrinadev.com/api/v1/tags \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Garantía",
"display_name": "Garantía"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "12121212-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "garantia",
"display_name": "Garantía",
"usage_count": 1,
"created_at": "2026-09-22T11:06:38.372Z"
}
}Responde: 200 · 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /tags/{id}
Delete a tag
Removes the tag and every attachment to it, across conversations and contacts alike. A 404 for an id that is not there.
It does not stay deleted if anything still uses the name: attaching by name re-creates it, and so does an agent acting on a suggestion. A tag that keeps coming back is being written by something, not resurrected.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/tags/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /tags/{id}
Fetch one tag
The tag by id, without the contact count the list can add.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/tags/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "12121212-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "garantia",
"display_name": "Garantía",
"usage_count": 1,
"created_at": "2026-09-22T11:06:38.372Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tags/{id}
Rename a tag’s display name
display_name is the ONLY editable field, and it is required. The slug name is permanent: it is the identity every attachment and every create-by-name call resolves through, so renaming it would detach the tag from itself.
In practice that means a tag can be relabelled for people without anything breaking, but its canonical name stays whatever it was first typed as. To change the slug, create a new tag and re-attach.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
display_name | string | sí | mín. 1, máx. 120 |
curl -X PUT https://api.vitrinadev.com/api/v1/tags/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Garantía extendida"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "12121212-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "garantia",
"display_name": "Garantía extendida",
"usage_count": 1,
"created_at": "2026-09-22T11:06:38.372Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tags/suggest
Ask the model to suggest tags for a conversation
Reads the conversation’s recent transcript and the workspace’s existing tags, and returns suggestions as \{ name, display_name, existing, tag_id \}.
existing: true means the slug is already a tag here and tag_id is set, so it can be attached directly. existing: false rows have a null tag_id and are attached by NAME, which creates them on first use — the suggestion itself creates nothing.
Suggestions are deduplicated by slug, so the model proposing both Prioridad and prioridad yields one row. Nothing is attached and nothing is stored; this is advisory.
Results are cached server-side per conversation, keyed off the newest message — a fresh message rotates the key automatically, so repeat opens of an unchanged conversation are served from cache with no LLM call. Pass force=true to bypass a live cache entry and recompute, overwriting it — this backs an explicit "refresh suggestions" action; do not set it on every load.
On a cache miss this is one LLM call over the conversation transcript — not free, and not something to poll. Registered before /\{id\} so suggest is never read as a tag id.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
conversation_id | query | uuid | sí | |
force | query | any | no |
curl https://api.vitrinadev.com/api/v1/tags/suggest \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"name": "garantia",
"display_name": "Garantía",
"existing": true,
"tag_id": "12121212-0000-4000-8000-000000000001"
},
{
"name": "coordinacion-de-visita",
"display_name": "coordinacion-de-visita",
"existing": false,
"tag_id": null
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429