VitrinaAPI

Teams

The routing destination channels and tickets hang off, and its own coverage-hours schedule.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Build teams and custom roles explains this resource in prose, with runnable examples.

MethodPathWhat it does
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"

Example response (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
  }
}

Answers: 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.

Body

FieldTypeRequiredConstraints
namestringyesmín. 1, máx. 120
brandstring—máx. 120
descriptionstring—má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"
      }
    }
  }'

Example response (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"
  }
}

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/teams/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /teams/{id}

Fetch one team

The team including its hours_mode and any override windows.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/teams/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (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"
  }
}

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
namestring—mín. 1, máx. 120
brandstring | null—máx. 120
descriptionstring | null—má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."
  }'

Example response (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"
  }
}

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/teams/<id>/members \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (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"
  }
}

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
user_iduuidyes
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"
  }'

Example response (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"
  }
}

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes
membershipIdpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/teams/<id>/members/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 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.

ParameterInTypeRequiredConstraints
idpathuuidyes
membershipIdpathuuidyes

Body

FieldTypeRequiredConstraints
rolelead \memberyes
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"
  }'

Example response (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"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

On this page