VitrinaAPI

Roles personalizados

El catálogo de scopes y visibilidad por rol — acceso efectivo, no una etiqueta.

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/rolesList the workspace’s custom roles
POST/rolesCreate a custom role
DELETE/roles/{id}Delete a custom role
GET/roles/{id}Fetch one custom role
PUT/roles/{id}Update a custom role

GET /roles

List the workspace’s custom roles

Newest first, with a total. Unpaginated — a workspace has a handful of custom roles. Built-in tiers are not returned: they are not rows on this table.

curl https://api.vitrinadev.com/api/v1/roles \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "d2d2d2d2-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "name": "Vendedor sucursal",
      "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
      "scopes": [
        "conversations:read",
        "conversations:write",
        "leads:read",
        "leads:write"
      ],
      "record_visibility": "assigned",
      "stock_visibility": "own_locations",
      "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 /roles

Create a custom role

scopes is optional; omitting it creates an EMPTY role, which grants nothing and is accepted. Only known, non-owner-only permissions are allowed — the wildcard *, an unknown string and the owner-only scopes (billing:write, tenant:delete) are all a 400.

record_visibility is all | assigned_unassigned | assigned and narrows conversations, tickets and leads by assignee. stock_visibility is all | own_locations and narrows VEHICLE reads to the sucursales on the member’s own workspace_member.location_ids — a member on an own_locations role with no sucursales sees no stock at all, and a vehicle with no location_id is invisible to such a member by definition. Both default to all.

Every field is priced against the caller’s own access: a scope you do not hold, a record_visibility wider than yours, or a stock_visibility wider than yours is a 403. Owners and unrestricted admins are unaffected — the ceilings can only ever bite a caller who is themselves restricted. A narrowly-scoped sk_ key cannot set record_visibility at all (a credential carries no such ceiling of its own, so there is nothing to bound the grant against); use a signed-in user.

Cuerpo

CampoTipoObligatorioRestricciones
namestringmín. 1, máx. 200
descriptionstring | nullmáx. 2000
tintstring | nullmáx. 40
scopesstring[]
record_visibilityall \assigned_unassigned \assigned
stock_visibilityall \own_locations
curl -X POST https://api.vitrinadev.com/api/v1/roles \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Vendedor sucursal",
    "scopes": [
      "conversations:read",
      "conversations:write",
      "leads:read",
      "leads:write"
    ],
    "record_visibility": "assigned",
    "stock_visibility": "own_locations"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "d2d2d2d2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Vendedor sucursal",
    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
    "scopes": [
      "conversations:read",
      "conversations:write",
      "leads:read",
      "leads:write"
    ],
    "record_visibility": "assigned",
    "stock_visibility": "own_locations",
    "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 /roles/{id}

Delete a custom role

A role that still has members is a 409: unlinking them would resolve each one to their BUILT-IN tier with record_visibility: all and stock_visibility: all, i.e. silently promote them. Empty the role first, or pass ?reassign_to=<role id> to move every member onto a named replacement in one step — the replacement is a grant and is priced on all three axes exactly as the body of a PUT is. reassign_to may not name the role being deleted (400), and reassignment to a built-in tier is not supported.

The delete also REVOKES every still-pending invitation that named the role, in the same transaction: that foreign key is ON DELETE SET NULL, so such an invitation would otherwise accept on its plain built-in tier. Accepted, expired and already-revoked invitations are untouched.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
reassign_toqueryuuidno
curl -X DELETE https://api.vitrinadev.com/api/v1/roles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

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

GET /roles/{id}

Fetch one custom role

The role including both visibility ceilings. A role belonging to another workspace is a 404, never a 403 — nothing here confirms a guessed id.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/roles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d2d2d2d2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Vendedor sucursal",
    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
    "scopes": [
      "conversations:read",
      "conversations:write",
      "leads:read",
      "leads:write"
    ],
    "record_visibility": "assigned",
    "stock_visibility": "own_locations",
    "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 /roles/{id}

Update a custom role

A PUT that behaves as a PATCH: only the fields present are applied and at least one is required. scopes is a FULL REPLACEMENT array, not a merge — but only the scopes being ADDED are priced, so an admin who lacks X can still rename a role that already carries it.

The change takes effect on the next request of every member already on the role, including the caller.

record_visibility is all | assigned_unassigned | assigned and narrows conversations, tickets and leads by assignee. stock_visibility is all | own_locations and narrows VEHICLE reads to the sucursales on the member’s own workspace_member.location_ids — a member on an own_locations role with no sucursales sees no stock at all, and a vehicle with no location_id is invisible to such a member by definition. Both default to all.

Every field is priced against the caller’s own access: a scope you do not hold, a record_visibility wider than yours, or a stock_visibility wider than yours is a 403. Owners and unrestricted admins are unaffected — the ceilings can only ever bite a caller who is themselves restricted. A narrowly-scoped sk_ key cannot set record_visibility at all (a credential carries no such ceiling of its own, so there is nothing to bound the grant against); use a signed-in user.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
namestringmín. 1, máx. 200
descriptionstring | nullmáx. 2000
tintstring | nullmáx. 40
scopesstring[]
record_visibilityall \assigned_unassigned \assigned
stock_visibilityall \own_locations
curl -X PUT https://api.vitrinadev.com/api/v1/roles/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "record_visibility": "assigned_unassigned"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d2d2d2d2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "name": "Vendedor sucursal",
    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
    "scopes": [
      "conversations:read",
      "conversations:write",
      "leads:read",
      "leads:write"
    ],
    "record_visibility": "assigned_unassigned",
    "stock_visibility": "own_locations",
    "created_at": "2026-01-10T13:00:00.000Z",
    "updated_at": "2026-09-10T13:00:00.000Z"
  }
}

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

En esta página