Equipos de trabajo
El destino de enrutamiento de canales y tickets, y su horario de cobertura propio.
Descarga la proyección completa de la API pública: openapi.json.
Organizar equipos y roles explica este recurso en prosa, con ejemplos ejecutables.
| Método | Ruta | Qué hace |
|---|---|---|
GET | /teams | List teams |
POST | /teams | Create a team |
DELETE | /teams/{id} | Delete a team |
GET | /teams/{id} | Fetch one team |
PUT | /teams/{id} | Update a team |
GET | /teams/{id}/members | List a team’s members |
POST | /teams/{id}/members | Add a member |
DELETE | /teams/{id}/members/{membershipId} | Remove a member |
PUT | /teams/{id}/members/{membershipId} | Change a member’s role |
GET /teams
List teams
Every team in the workspace, with a total. Unfiltered and unpaginated — workspaces have a handful of teams, not thousands.
curl https://api.vitrinadev.com/api/v1/teams \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "cccccccc-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas Providencia",
"brand": "Providencia",
"description": "El equipo que atiende el canal WhatsApp de la sucursal.",
"hours_mode": "override",
"hours": {
"mon": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"tue": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"sat": {
"on": true,
"start": "10:00",
"end": "14:00"
}
},
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
],
"meta": {
"total": 1
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /teams
Create a team
name is unique per workspace — a collision is a 409, not a silent second team with the same name.
hours is only kept when hours_mode is "override". The mode defaults to "workspace", so a create that sends windows without also setting the mode stores null and answers 201 as though it worked. Send both together. hours is a partial weekday map (mon…sun) of \{ on, start, end \}, each time HH:MM. A weekday you leave out is not covered rather than inherited — the override replaces the workspace week, it does not merge into it.
brand is the free-text label that ties a team to a channel in the operator UI; it is not validated against anything.
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
name | string | sí | mín. 1, máx. 120 |
brand | string | — | máx. 120 |
description | string | — | máx. 2000 |
hours_mode | workspace \ | override | — |
hours | object | null | — |
curl -X POST https://api.vitrinadev.com/api/v1/teams \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ventas Providencia",
"brand": "Providencia",
"hours_mode": "override",
"hours": {
"mon": {
"on": true,
"start": "09:00",
"end": "19:00"
}
}
}'Ejemplo de respuesta (201)
{
"data": {
"id": "cccccccc-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas Providencia",
"brand": "Providencia",
"description": "El equipo que atiende el canal WhatsApp de la sucursal.",
"hours_mode": "override",
"hours": {
"mon": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"tue": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"sat": {
"on": true,
"start": "10:00",
"end": "14:00"
}
},
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /teams/{id}
Delete a team
A hard delete with fan-out you should expect: memberships cascade away, and every messaging_account.default_team_id, lead.team_id and appointment sales_team_id pointing here is set to NULL. Channels that routed to this team silently stop routing anywhere.
ticket.team_id is a bare column with no foreign key, so open tickets keep pointing at the deleted id and their coverage clock falls back to the workspace schedule. Reassign them before deleting if that matters.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/teams/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /teams/{id}
Fetch one team
The team including its hours_mode and any override windows.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/teams/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "cccccccc-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas Providencia",
"brand": "Providencia",
"description": "El equipo que atiende el canal WhatsApp de la sucursal.",
"hours_mode": "override",
"hours": {
"mon": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"tue": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"sat": {
"on": true,
"start": "10:00",
"end": "14:00"
}
},
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /teams/{id}
Update a team
A PUT that behaves as a PATCH: only the fields present are applied and at least one is required.
The hours rules are not symmetric with create, so read them separately. Setting hours_mode: "workspace" CLEARS the stored override in the same write — the windows are gone, not parked, and flipping back to "override" later resumes with nothing. Conversely, sending hours while the mode is "workspace" stores windows that have no effect until the mode is switched. hours is a partial weekday map (mon…sun) of \{ on, start, end \}, each time HH:MM. A weekday you leave out is not covered rather than inherited — the override replaces the workspace week, it does not merge into it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
name | string | — | mín. 1, máx. 120 |
brand | string | null | — | máx. 120 |
description | string | null | — | máx. 2000 |
hours_mode | workspace \ | override | — |
hours | object | null | — |
curl -X PUT https://api.vitrinadev.com/api/v1/teams/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Turno mañana y tarde."
}'Ejemplo de respuesta (200)
{
"data": {
"id": "cccccccc-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas Providencia",
"brand": "Providencia",
"description": "Turno mañana y tarde.",
"hours_mode": "override",
"hours": {
"mon": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"tue": {
"on": true,
"start": "09:00",
"end": "19:00"
},
"sat": {
"on": true,
"start": "10:00",
"end": "14:00"
}
},
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /teams/{id}/members
List a team’s members
The membership rows, with total and team_id in the envelope. Each row carries user_id, role, added_by and added_at — this is the membership record, not the user profile, so names and emails have to be joined from /memberships.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/teams/<id>/members \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "e7e7e7e7-0000-4000-8000-000000000001",
"team_id": "cccccccc-0000-4000-8000-000000000001",
"user_id": "11111111-0000-4000-8000-000000000001",
"role": "lead",
"added_by": "11111111-0000-4000-8000-000000000001",
"added_at": "2026-01-10T13:05:00.000Z"
}
],
"meta": {
"total": 1,
"team_id": "cccccccc-0000-4000-8000-000000000001"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /teams/{id}/members
Add a member
user_id must already be an active member of this workspace — an unknown user, one from another workspace, or a deactivated one is refused with a 400. That check is what stops teams:write being used to graft an arbitrary user id onto a team, so it is deliberate rather than incidental validation.
role defaults to member. Adding the same user twice is a 409 (one membership per user per team). added_by records the human who made the change and is NULL when an sk_* API key does it, since a key has no subject.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
user_id | uuid | sí | |
role | lead \ | member | — |
curl -X POST https://api.vitrinadev.com/api/v1/teams/<id>/members \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "e6e6e6e6-0000-4000-8000-000000000002",
"role": "member"
}'Ejemplo de respuesta (201)
{
"data": {
"id": "e7e7e7e7-0000-4000-8000-000000000001",
"team_id": "cccccccc-0000-4000-8000-000000000001",
"user_id": "e6e6e6e6-0000-4000-8000-000000000002",
"role": "member",
"added_by": "11111111-0000-4000-8000-000000000001",
"added_at": "2026-01-10T13:05:00.000Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /teams/{id}/members/{membershipId}
Remove a member
Removes the membership only — the user keeps their workspace account and any other team. Tickets already assigned to them keep the assignment: ticket.assignee_user_id is deliberately not a foreign key, so work in flight is not orphaned by a team change.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
membershipId | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/teams/<id>/members/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
PUT /teams/{id}/members/{membershipId}
Change a member’s role
Moves a membership between lead and member. The role is a label on the team, not a permission grant: what a user may DO is decided by their workspace role and scopes, so promoting someone to lead here does not widen their access.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
membershipId | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
role | lead \ | member | sí |
curl -X PUT https://api.vitrinadev.com/api/v1/teams/<id>/members/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"role": "lead"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "e7e7e7e7-0000-4000-8000-000000000001",
"team_id": "cccccccc-0000-4000-8000-000000000001",
"user_id": "11111111-0000-4000-8000-000000000001",
"role": "lead",
"added_by": "11111111-0000-4000-8000-000000000001",
"added_at": "2026-01-10T13:05:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
Etiquetas
El vocabulario suelto que se cuelga de una conversación o de un contacto: adjuntar por nombre lo crea la primera vez.
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.