Tickets
El caso que sobrevive al hilo: puede dueñar varias conversaciones, vive en una columna de un tablero y tiene responsable y SLA.
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 | /tickets/{id} | Get ticket |
PUT | /tickets/{id}/assign | Assign a ticket to a teammate or a team |
GET | /tickets/{id}/assignment-events | Every contratista step on a caso (timeline) |
GET | /tickets/{id}/assignments | The contratistas a caso was sent to |
POST | /tickets/{id}/assignments | Assign a caso (or some of its observaciones) to a contratista |
GET | /tickets/{id}/case | A post-venta caso: unidad, observaciones, fotos, visita, encuesta |
PATCH | /tickets/{id}/case | Set a caso’s priority or unidad |
POST | /tickets/{id}/claim | Claim a ticket for the caller |
PUT | /tickets/{id}/close | Resolve a ticket (the manual "done" action) |
GET | /tickets/{id}/conversations | The conversations this ticket owns |
POST | /tickets/{id}/media | Attach photos or videos to a caso |
GET | /tickets/{id}/messages | One timeline across every conversation on this ticket |
POST | /tickets/{id}/observations | Add an observación (a defect) to a caso |
PATCH | /tickets/{id}/observations/{observationId} | Correct or resolve an observación |
PUT | /tickets/{id}/snooze | Snooze a ticket until a wake-up time |
PUT | /tickets/{id}/stage | Move a ticket to another column |
PUT | /tickets/{id}/status | Set the ticket status |
POST | /tickets/{id}/visit | Schedule the technical visit of a caso |
GET | /tickets/stage/{stageId} | Tickets on one column of a board |
GET | /tickets/status-counts | Ticket counts by lifecycle state |
GET | /tickets/today-counts | Ticket counts for tickets opened today |
GET /tickets/{id}
Get ticket
The ticket row. origin_conversation_id is the thread it was opened from; every thread attached to it is GET /tickets/\{id\}/conversations. display_id (T-1247) is the label people quote — a path accepts it, nothing stores it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/tickets/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "open",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": null,
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tickets/{id}/assign
Assign a ticket to a teammate or a team
Send at least one of handler, assignee_user_id, team_id; an empty body is a 400. null on either id CLEARS it, omitting the key leaves it alone — two different requests, and the server tells them apart. Assigning drops a system line in the origin conversation naming the author («Camila», «Camila vía Claude», «CRM propio (API)»).
This is the key-friendly counterpart of POST /tickets/\{id\}/claim.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
handler | bot \ | human \ | external |
assignee_user_id | string | null | — | |
team_id | string | null | — |
curl -X PUT https://api.vitrinadev.com/api/v1/tickets/<id>/assign \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "open",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": null,
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/{id}/assignment-events
Every contratista step on a caso (timeline)
assigned, notified, viewed, accepted, declined, visit_scheduled, media_added, done, cancelled — newest first, with who did it (staff, contratista, system). The same steps also appear as system lines in the caso’s conversation thread. Requires tickets:read. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/tickets/<id>/assignment-events \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "0199e3a0-0000-7000-8000-000000000a31",
"assignment_id": "0199e3a0-0000-7000-8000-000000000a21",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"kind": "accepted",
"actor": "contratista",
"data": {},
"created_at": "2026-10-20T13:00:00.000Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/{id}/assignments
The contratistas a caso was sent to
Newest first, with the contratista’s name, trade and the observaciones sent. Requires tickets:read. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/tickets/<id>/assignments \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "0199e3a0-0000-7000-8000-000000000a21",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
"status": "assigned",
"brief": "Revisar la filtración detrás del WC.",
"owner_window": "Lunes a viernes de 9 a 18",
"appointment_id": null,
"visit_starts_at": null,
"notified_at": "2026-10-20T12:01:00.000Z",
"notify_error": null,
"accepted_at": null,
"scheduled_at": null,
"done_at": null,
"declined_at": null,
"decline_reason": null,
"completion_notes": null,
"created_at": "2026-10-20T12:00:00.000Z",
"contratista_name": "Gasfitería Araucanía",
"contratista_trade": "gasfitería",
"contratista_whatsapp": "+56912345678",
"observation_ids": [
"0199e3a0-0000-7000-8000-000000000b01"
]
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tickets/{id}/assignments
Assign a caso (or some of its observaciones) to a contratista
Sends the caso to a contratista: they get a WhatsApp with a no-login link (/caso/<token>, 30 days) where they accept or decline, schedule the visit, upload before / after photos and close. Without observation_ids every open observación not already sent goes; an observación already with another live contratista is a 409. Assigning the same contratista again reuses the live assignment. The result says whether the WhatsApp went out (notified); a closed 24 h window or a missing number comes back in notify_error, and the link can be copied instead. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X POST https://api.vitrinadev.com/api/v1/tickets/<id>/assignments \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
"brief": "Revisar la filtración detrás del WC.",
"owner_window": "Lunes a viernes de 9 a 18"
}'Ejemplo de respuesta (201)
{
"data": {
"assignment": {
"id": "0199e3a0-0000-7000-8000-000000000a21",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
"status": "assigned",
"brief": "Revisar la filtración detrás del WC.",
"owner_window": "Lunes a viernes de 9 a 18",
"appointment_id": null,
"visit_starts_at": null,
"notified_at": "2026-10-20T12:01:00.000Z",
"notify_error": null,
"accepted_at": null,
"scheduled_at": null,
"done_at": null,
"declined_at": null,
"decline_reason": null,
"completion_notes": null,
"created_at": "2026-10-20T12:00:00.000Z",
"contratista_name": "Gasfitería Araucanía",
"contratista_trade": "gasfitería",
"contratista_whatsapp": "+56912345678",
"observation_ids": [
"0199e3a0-0000-7000-8000-000000000b01"
]
},
"link": "https://app.example.com/caso/eyJhbGciOi…",
"notified": true,
"notify_error": null,
"observation_ids": [
"0199e3a0-0000-7000-8000-000000000b01"
]
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/{id}/case
A post-venta caso: unidad, observaciones, fotos, visita, encuesta
The ticket seen as a caso de post-venta: its proyecto and unidad, every observación with its garantía (computed from the dates on record), the owner’s photos and videos (copied in from the caso’s conversations on read) and staff uploads, the next technical visita and the owner’s own 1 to 5 rating. \{id\} is the ticket uuid or its T-number. Requires tickets:read. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/tickets/<id>/case \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"display_id": "T-5",
"priority": "alta",
"project": {
"id": "0199e3a0-0000-7000-8000-000000000101",
"name": "Vista Golf Departamentos"
},
"unit": {
"id": "0199e3a0-0000-7000-8000-000000000401",
"display_id": "I-42",
"unit_number": "302",
"kind": "departamento"
},
"observations": [
{
"id": "0199e3a0-0000-7000-8000-000000000b01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal, hace dos meses.",
"warranty_years": 5,
"warranty_base": "dom",
"warranty_start_at": "2024-03-15",
"warranty_expires_at": "2029-03-15",
"warranty_status": "dentro_de_plazo",
"needs_review": false,
"status": "open",
"created_by": null,
"created_at": "2026-10-20T12:00:00.000Z",
"resolved_at": null
}
],
"media": [
{
"id": "0199e3a0-0000-7000-8000-000000000f01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"observation_id": null,
"url": "https://media.example.com/conversations/abc/filtracion.jpg",
"media_type": "image",
"source": "conversation",
"message_id": null,
"caption": null,
"created_at": "2026-10-20T12:01:00.000Z"
}
],
"next_visit": {
"appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
"starts_at": "2026-10-22T13:00:00.000Z",
"ends_at": "2026-10-22T14:30:00.000Z",
"status": "confirmed"
},
"survey": {
"asked_at": null,
"score": null
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /tickets/{id}/case
Set a caso’s priority or unidad
Staff set the priority (emergencia pushes the proyecto’s on-call people at once, bell + push) and/or the unidad. The unidad must be one the ticket’s contact owns (unit_owner), else 400; the proyecto follows from it. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X PATCH https://api.vitrinadev.com/api/v1/tickets/<id>/case \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"priority": "alta",
"property_id": "0199e3a0-0000-7000-8000-000000000401"
}'Ejemplo de respuesta (200)
{
"data": {
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"display_id": "T-5",
"priority": "alta",
"project": {
"id": "0199e3a0-0000-7000-8000-000000000101",
"name": "Vista Golf Departamentos"
},
"unit": {
"id": "0199e3a0-0000-7000-8000-000000000401",
"display_id": "I-42",
"unit_number": "302",
"kind": "departamento"
},
"observations": [
{
"id": "0199e3a0-0000-7000-8000-000000000b01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal, hace dos meses.",
"warranty_years": 5,
"warranty_base": "dom",
"warranty_start_at": "2024-03-15",
"warranty_expires_at": "2029-03-15",
"warranty_status": "dentro_de_plazo",
"needs_review": false,
"status": "open",
"created_by": null,
"created_at": "2026-10-20T12:00:00.000Z",
"resolved_at": null
}
],
"media": [
{
"id": "0199e3a0-0000-7000-8000-000000000f01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"observation_id": null,
"url": "https://media.example.com/conversations/abc/filtracion.jpg",
"media_type": "image",
"source": "conversation",
"message_id": null,
"caption": null,
"created_at": "2026-10-20T12:01:00.000Z"
}
],
"next_visit": {
"appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
"starts_at": "2026-10-22T13:00:00.000Z",
"ends_at": "2026-10-22T14:30:00.000Z",
"status": "confirmed"
},
"survey": {
"asked_at": null,
"score": null
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tickets/{id}/claim
Claim a ticket for the caller
Requires a workspace member — a Supabase session or a personal token acting as one — since the caller BECOMES the assignee, and an sk_* API key has no member behind it to assign the ticket to; it gets 403. An API key assigns to a SPECIFIC member instead: PUT /tickets/\{id\}/assign.
Claiming a ticket somebody else already holds is not an error: the response carries alreadyClaimed: true and the current assignee, so two people pressing at once get the same answer rather than a race. Takes tickets:claim, not tickets:write.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X POST https://api.vitrinadev.com/api/v1/tickets/<id>/claim \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"ticketId": "dddddddd-0000-4000-8000-000000000001",
"assigneeUserId": "11111111-0000-4000-8000-000000000001",
"alreadyClaimed": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tickets/{id}/close
Resolve a ticket (the manual "done" action)
⚠ Despite the path, this RESOLVES: status becomes resolved, the handler flips to human, resolved_by/resolved_at are stamped and any snooze is cleared. It is the manual counterpart of the auto-resolve worker, and the path name predates the resolved/closed split. The terminal closed state is reached by the auto-close worker a grace window later, or by PUT /tickets/\{id\}/status with closed.
This is the operation that fires ticket.resolved.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X PUT https://api.vitrinadev.com/api/v1/tickets/<id>/close \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "resolved",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": null,
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": "human",
"resolved_at": "2026-09-22T11:15:06.653Z",
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/{id}/conversations
The conversations this ticket owns
Every thread attached to the case, across channels — the WhatsApp conversation it started on and the email one the customer opened later. Each row carries the contact identity the inbox shows beside it, so a client does not need a contacts:read call to label the list.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/tickets/<id>/conversations \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "bbbbbbbb-0000-4000-8000-000000000001",
"external_id": "web:visitor-9f2c1a",
"channel": "web",
"brand": null,
"contact_id": "22222222-0000-4000-8000-000000000001",
"contact_name": "Rodrigo Pizarro",
"contact_phone": "+56977000021",
"messaging_account_id": "16161616-0000-4000-8000-000000000001",
"ticket_id": "dddddddd-0000-4000-8000-000000000001",
"last_message_date": "2026-09-22T11:08:10.882Z",
"created_at": "2026-09-22T11:06:33.744Z",
"updated_at": "2026-09-22T11:06:33.744Z"
},
{
"id": "bbbbbbbb-0000-4000-8000-000000000002",
"external_id": "[email protected]",
"channel": "email",
"brand": null,
"contact_id": "22222222-0000-4000-8000-000000000001",
"contact_name": "Rodrigo Pizarro",
"contact_phone": "+56977000021",
"messaging_account_id": "16161616-0000-4000-8000-000000000001",
"ticket_id": "dddddddd-0000-4000-8000-000000000001",
"last_message_date": "2026-09-22T12:02:11.000Z",
"created_at": "2026-09-22T12:01:44.000Z",
"updated_at": "2026-09-22T12:02:11.000Z"
}
],
"meta": {
"total": 2,
"ticket_id": "dddddddd-0000-4000-8000-000000000001"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tickets/{id}/media
Attach photos or videos to a caso
Staff uploads (source: staff), optionally on one observación. Upload the file with POST /tenant/media first and send its url. A url already on the caso is left as it is. The owner’s own WhatsApp media attach on their own. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X POST https://api.vitrinadev.com/api/v1/tickets/<id>/media \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"url": "https://media.example.com/t/other/visita-1.jpg",
"observation_id": "0199e3a0-0000-7000-8000-000000000b01",
"caption": "Después de la reparación"
}
]
}'Ejemplo de respuesta (201)
{
"data": {
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"display_id": "T-5",
"priority": "alta",
"project": {
"id": "0199e3a0-0000-7000-8000-000000000101",
"name": "Vista Golf Departamentos"
},
"unit": {
"id": "0199e3a0-0000-7000-8000-000000000401",
"display_id": "I-42",
"unit_number": "302",
"kind": "departamento"
},
"observations": [
{
"id": "0199e3a0-0000-7000-8000-000000000b01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal, hace dos meses.",
"warranty_years": 5,
"warranty_base": "dom",
"warranty_start_at": "2024-03-15",
"warranty_expires_at": "2029-03-15",
"warranty_status": "dentro_de_plazo",
"needs_review": false,
"status": "open",
"created_by": null,
"created_at": "2026-10-20T12:00:00.000Z",
"resolved_at": null
}
],
"media": [
{
"id": "0199e3a0-0000-7000-8000-000000000f01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"observation_id": null,
"url": "https://media.example.com/conversations/abc/filtracion.jpg",
"media_type": "image",
"source": "conversation",
"message_id": null,
"caption": null,
"created_at": "2026-10-20T12:01:00.000Z"
}
],
"next_visit": {
"appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
"starts_at": "2026-10-22T13:00:00.000Z",
"ends_at": "2026-10-22T14:30:00.000Z",
"status": "confirmed"
},
"survey": {
"asked_at": null,
"score": null
}
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/{id}/messages
One timeline across every conversation on this ticket
The case's whole history, interleaved in time: messages from every attached conversation, each stamped with the channel it arrived on, plus the system lines that record assignments and status changes (type: "event"). ?before pages backwards; the default page is 500 messages, the maximum 2 000.
This is a flattened view for reading. For the per-message delivery fields and the author of each one, read a conversation directly: GET /conversations/\{id\}/messages.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
limit | query | integer | no | ≥ 1, ≤ 2000, por defecto 500 |
before | query | string | no | date-time |
curl https://api.vitrinadev.com/api/v1/tickets/<id>/messages \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "eeeeeeee-0000-4000-8000-000000000001",
"conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"channel": "web",
"role": "user",
"content": "¿Me pueden confirmar la hora del jueves?",
"type": "text",
"tool_calls": null,
"tool_call_id": null,
"created_at": "2026-09-22T11:07:02.824Z"
},
{
"id": "eeeeeeee-0000-4000-8000-000000000002",
"conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"channel": "web",
"role": "system",
"content": "CRM propio (API) reasignó la conversación",
"type": "event",
"tool_calls": null,
"tool_call_id": null,
"created_at": "2026-09-22T11:06:54.411Z"
}
],
"meta": {
"ticket_id": "dddddddd-0000-4000-8000-000000000001",
"limit": 500
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tickets/{id}/observations
Add an observación (a defect) to a caso
One conversation can report several defects, so one caso holds several observaciones, each with its own partida and garantía. The plazo comes from the law (terminaciones 3 años from the unidad’s inscripción en el CBR; instalaciones and techumbre 5, estructura 10, from the proyecto’s recepción definitiva) and the dates on record: dentro_de_plazo, fuera_de_plazo or por_revisar (a date is missing). An out-of-plazo defect is still registered and flagged needs_review. 400 when the same partida and description are already on the caso. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X POST https://api.vitrinadev.com/api/v1/tickets/<id>/observations \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal."
}'Ejemplo de respuesta (201)
{
"data": {
"id": "0199e3a0-0000-7000-8000-000000000b01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal, hace dos meses.",
"warranty_years": 5,
"warranty_base": "dom",
"warranty_start_at": "2024-03-15",
"warranty_expires_at": "2029-03-15",
"warranty_status": "dentro_de_plazo",
"needs_review": false,
"status": "open",
"created_by": null,
"created_at": "2026-10-20T12:00:00.000Z",
"resolved_at": null
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /tickets/{id}/observations/{observationId}
Correct or resolve an observación
Changing the partida or the years recomputes the garantía. status: resolved stamps the resolution time. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
observationId | path | uuid | sí |
curl -X PATCH https://api.vitrinadev.com/api/v1/tickets/<id>/observations/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "resolved"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "0199e3a0-0000-7000-8000-000000000b01",
"ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
"partida": "instalaciones_sanitarias",
"description": "Filtración detrás del WC del baño principal, hace dos meses.",
"warranty_years": 5,
"warranty_base": "dom",
"warranty_start_at": "2024-03-15",
"warranty_expires_at": "2029-03-15",
"warranty_status": "dentro_de_plazo",
"needs_review": false,
"status": "resolved",
"created_by": null,
"created_at": "2026-10-20T12:00:00.000Z",
"resolved_at": null
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tickets/{id}/snooze
Snooze a ticket until a wake-up time
until is an ISO 8601 timestamp. The ticket leaves the open queue and comes back by itself; a new inbound message on any of its conversations wakes it early.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
until | string | sí | date-time |
curl -X PUT https://api.vitrinadev.com/api/v1/tickets/<id>/snooze \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"until": "2026-09-23T14:00:00.000Z"
}'Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "snoozed",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": "2026-09-23T14:00:00.000Z",
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tickets/{id}/stage
Move a ticket to another column
Moves the card. The move is rejected with a 400 when the current column's allowed_transitions_to does not name the target, or when the target belongs to another board — read them from GET /conversations/\{id\}/linked-records or GET /pipelines/\{id\}.
reason is recorded on the transition log beside the author. suggestion_id marks a Copilot stage suggestion as accepted, and is honoured only when that suggestion actually targets THIS ticket — a mismatched id is ignored rather than refused, so the move still lands.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
stageId | uuid | sí | |
reason | string | — | máx. 500 |
suggestion_id | uuid | — |
curl -X PUT https://api.vitrinadev.com/api/v1/tickets/<id>/stage \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"stageId": "55555555-0000-4000-8000-000000000012",
"reason": "El equipo ya tiene la boleta"
}'Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "open",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000012",
"snoozed_until": null,
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /tickets/{id}/status
Set the ticket status
open · pending · snoozed · resolved · closed. snoozed_until is REQUIRED when status is snoozed and ignored otherwise — PUT /tickets/\{id\}/snooze is the same move with a body that cannot forget the time.
Setting resolved here does NOT fire ticket.resolved: that Evento is emitted by PUT /tickets/\{id\}/close and by the conversation cascade, which are the paths that also stamp resolved_by/resolved_at. Resolve through one of those if a receiver is watching.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
status | open \ | pending \ | snoozed \ |
snoozed_until | string | — | date-time |
curl -X PUT https://api.vitrinadev.com/api/v1/tickets/<id>/status \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "snoozed",
"snoozed_until": "2026-09-23T14:00:00.000Z"
}'Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "snoozed",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": "2026-09-23T14:00:00.000Z",
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /tickets/{id}/visit
Schedule the technical visit of a caso
Books the visita as an appointment linked to the caso and tells the owner on the caso’s conversation (unless notify_owner is false). One live visit per caso: scheduling again cancels the previous one. The agent answers «¿cuándo viene el técnico?» from it. Requires tickets:write. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 MODULE_NOT_ENABLED.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | string | sí | mín. 1 |
curl -X POST https://api.vitrinadev.com/api/v1/tickets/<id>/visit \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"starts_at": "2026-10-22T13:00:00.000Z",
"ends_at": "2026-10-22T14:30:00.000Z"
}'Ejemplo de respuesta (201)
{
"data": {
"appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
"starts_at": "2026-10-22T13:00:00.000Z",
"ends_at": "2026-10-22T14:30:00.000Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/stage/{stageId}
Tickets on one column of a board
The cards in a column, paged. This is how a board is read: one call per column, with the columns themselves coming from GET /pipelines/\{id\}. A stage of a non-ticket board simply has no tickets on it and answers an empty page.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
stageId | path | string | sí | mín. 1 |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 1, ≤ 100, por defecto 20 |
sortBy | query | created_at \ | updated_at \ | status |
sortOrder | query | asc \ | desc | no |
curl https://api.vitrinadev.com/api/v1/tickets/stage/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "dddddddd-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"display_seq": 6,
"display_id": "T-6",
"status": "open",
"handler": "human",
"assignee_user_id": "11111111-0000-4000-8000-000000000001",
"assignee_ai_agent_graph_id": null,
"team_id": "cccccccc-0000-4000-8000-000000000001",
"contact_id": "22222222-0000-4000-8000-000000000001",
"lead_id": null,
"origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
"current_stage_id": "55555555-0000-4000-8000-000000000011",
"snoozed_until": null,
"reason": "El cliente pide reagendar su visita",
"opened_by": "human",
"opened_via": "admin_ui",
"resolved_by": null,
"resolved_at": null,
"brand": null,
"created_at": "2026-09-22T11:06:47.738Z",
"updated_at": "2026-09-22T11:06:54.183Z"
}
],
"meta": {
"pagination": {
"total": 1,
"page": 1,
"limit": 20
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/status-counts
Ticket counts by lifecycle state
One number per state for the whole workspace, plus two cross-cuts: human (tickets a person is handling) and marketing. closing is the grace window between a resolve and the auto-close, so it is not one of the status values a ticket ever holds — it is a count of resolved tickets whose close timer is running.
curl https://api.vitrinadev.com/api/v1/tickets/status-counts \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"total": 5,
"open": 1,
"pending": 0,
"snoozed": 0,
"resolved": 0,
"closed": 4,
"closing": 0,
"human": 1,
"marketing": 0
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /tickets/today-counts
Ticket counts for tickets opened today
The same buckets as /tickets/status-counts, narrowed to tickets OPENED today in the workspace's timezone — the day's workload, not the backlog.
curl https://api.vitrinadev.com/api/v1/tickets/today-counts \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"total": 1,
"open": 1,
"pending": 0,
"snoozed": 0,
"resolved": 0,
"closed": 0,
"closing": 0,
"human": 1,
"marketing": 0
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
Ajustes del workspace
La identidad general del workspace (nombre, zona horaria, idioma, moneda, sitio web) y las decisiones de Vitrina Ads: cómo se respondió el paso del tag y la meta mensual.
Credenciales de herramientas
La bóveda detrás de una herramienta personalizada autenticada — el secreto nunca vuelve a mostrarse, solo un preview.