VitrinaAPI

Equipos de trabajo

El destino de enrutamiento de canales y tickets, y su horario de cobertura propio.

Beta
Puede cambiar en cualquier momento, con una entrada en el changelog y aviso a quienes la llamaron recientemente — ver versionado.

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étodoRutaQué hace
GET/teamsList teams
POST/teamsCreate a team
DELETE/teams/{id}Delete a team
GET/teams/{id}Fetch one team
PUT/teams/{id}Update a team
GET/teams/{id}/membersList a team’s members
POST/teams/{id}/membersAdd 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 (monsun) 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

CampoTipoObligatorioRestricciones
namestringmín. 1, máx. 120
brandstringmáx. 120
descriptionstringmáx. 2000
hours_modeworkspace \override
hoursobject | 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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid
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 (monsun) 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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
namestringmín. 1, máx. 120
brandstring | nullmáx. 120
descriptionstring | nullmáx. 2000
hours_modeworkspace \override
hoursobject | 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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
user_iduuid
rolelead \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ámetroEnTipoObligatorioRestricciones
idpathuuid
membershipIdpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid
membershipIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
rolelead \member
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

En esta página