
# API de equipo

Tu equipo es todo aquel que trabaja dentro de tu cuenta además de ti (administradores, agentes y espectadores de solo lectura), además de las invitaciones que has enviado y los departamentos en los que los organizas. La API de equipo es la versión programática de **Configuración → Equipo**: añade y elimina personas, establece lo que cada una puede ver y hacer, envía y gestiona invitaciones, y administra departamentos.

Todos los endpoints a continuación son relativos a la URL base `https://api.youraiconnector.com/v1`. Para la versión del panel de control de todo lo que aparece en esta página, consulta [Gestión de equipo](../settings/team-management.md).

---

## Autenticación: estos endpoints requieren una persona que haya iniciado sesión

**Esta es la única parte de la API que no puede utilizar una clave de API.** Todos los endpoints `/team`, excepto los de [departamento](#departments), deben llamarse con un **token de ID de Firebase** de una sesión iniciada:

```
Authorization: Bearer <Firebase ID token>
```

Si envías una clave de API, la solicitud será rechazada con un `401`:

```json
{
  "success": false,
  "error_code": 401,
  "error": "This endpoint requires a Firebase ID token (Authorization: Bearer <token>)."
}
```

La razón es que estos endpoints deciden qué hacer basándose en **quién ha iniciado sesión**: tu rol, el límite de lo que tienes permitido conceder a otra persona y si actualmente estás trabajando dentro de otra cuenta. Una clave de API es una integración, no una persona, por lo que no hay nadie a quien aplicar esas reglas.

En la práctica, esto significa que la API de equipo es para una aplicación propia con un usuario <span data-t="appName">Your AI Connector</span> que haya iniciado sesión (consulta [Autenticación → Token de ID de Firebase](authentication.md#4-firebase-id-token-first-party-only)). Una integración servidor a servidor no puede gestionar miembros del equipo; no hay forma de generar uno de estos tokens desde fuera de la aplicación.

> **La excepción:** los cuatro endpoints de [departamento](#departments) son endpoints de API ordinarios. Aceptan tu clave de API exactamente igual que el resto de la API, así como una sesión iniciada.

Cada respuesta en esta página sigue el sobre habitual: `success: true` más los campos del endpoint en el nivel superior, o `success: false` con `error` y `error_code` cuando algo sale mal.

---

## Roles y permisos

Cada miembro del equipo tiene un **rol**, que establece su acceso predeterminado en 12 áreas de la aplicación. Luego puedes anular áreas individuales.

| Rol | Valor | Resumen |
|---|---|---|
| Admin | `admin` | Todo excepto las acciones de nivel de facturación del propietario. |
| Editor | `editor` | Puede crear y cambiar cosas. Se muestra como **Agente** en la aplicación. |
| Viewer | `viewer` | Solo lectura. |

Cada área se establece en uno de cuatro niveles: `none` (oculto), `view` (solo lectura), `edit` (crear y cambiar), `full` (incluyendo eliminar).

| Área | Admin | Editor | Viewer |
|---|---|---|---|
| `campaigns` | full | edit | view |
| `contacts` | full | edit | view |
| `messages` | full | edit | view |
| `appointments` | full | edit | view |
| `settings` | edit | view | none |
| `billing` | edit | none | none |
| `team_management` | edit | none | none |
| `analytics` | full | view | view |
| `phone_numbers` | edit | none | none |
| `integrations` | edit | none | none |
| `faqs` | full | edit | view |
| `daily_summaries` | full | view | view |

Para apartarse de los valores predeterminados del rol, envíe `permission_overrides`: una matriz de objetos `{ "area": ..., "level": ... }`. Cada entrada reemplaza el valor predeterminado del rol para esa área específica; todo lo que no incluya en la lista mantendrá el valor predeterminado del rol.

```json
"permission_overrides": [
  { "area": "analytics", "level": "full" },
  { "area": "billing", "level": "none" }
]
```

**Quién puede llamar a estos endpoints**

- El **propietario de la cuenta** siempre puede hacerlo todo.
- Un miembro del equipo necesita `team_management` en `view` para leer la lista de miembros y la lista de invitaciones, y en `edit` para añadir, cambiar, suspender, eliminar, invitar, cancelar o reenviar. Los administradores tienen `edit` de forma predeterminada; los editores y los visores tienen `none`, por lo que, de forma predeterminada, solo los administradores pueden gestionar el equipo.
- **Nadie puede conceder un acceso superior al suyo propio.** Si intenta asignar a alguien un nivel que usted mismo no posee, o editar, suspender o eliminar a alguien cuyo acceso ya es más amplio que el suyo, la solicitud será rechazada con `403` y un mensaje indicando el área.

---

## El objeto de miembro del equipo

`GET /team/members` devuelve uno de estos por miembro:

| Campo | Tipo | Descripción |
|---|---|---|
| `member_uid` | string | El ID de usuario del propio miembro. Este es el `{memberUid}` en las rutas a continuación. |
| `account_owner_uid` | string | La cuenta de la que son miembros. |
| `member_email` | string | Su dirección de correo electrónico. |
| `member_display_name` | string | El nombre que se muestra para ellos en la aplicación. |
| `role` | string | `admin`, `editor` o `viewer`. |
| `permission_overrides` | array | Sus excepciones por área. `[]` cuando se basan puramente en los valores predeterminados del rol. |
| `status` | string | `active` o `suspended`. |
| `auto_assign_enabled` | boolean \| null | Si los nuevos contactos pueden asignárseles automáticamente. `null` significa que nunca se ha cambiado, lo cual se comporta como `true`. |
| `created_by` | string | Quién los añadió. |
| `created_at` | string \| null | Marca de tiempo ISO 8601. |
| `updated_at` | string \| null | Marca de tiempo ISO 8601. |

Los miembros eliminados no se devuelven; la lista solo incluye a los miembros activos y suspendidos.

> **Los límites de visibilidad son de solo escritura aquí.** `contact_scope`, `contact_scope_axes` y `sub_account_access` (consulte [Limitar lo que un miembro puede ver](#limiting-what-a-member-can-see)) se pueden establecer al crear, actualizar e invitar, pero este endpoint no los devuelve.

---

## Listar miembros del equipo

`GET /team/members`

Devuelve la lista de miembros junto con el recuento de plazas de su plan, para que pueda mostrar "3 de 5 plazas" y saber cuándo se rechazará una invitación.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/team/members" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/team/members", {
  headers: { Authorization: `Bearer ${idToken}` },
});
const { members, seat_limit, seats_used } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/team/members",
    headers={"Authorization": f"Bearer {id_token}"},
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "members": [
    {
      "account_owner_uid": "owner_uid_123",
      "member_uid": "uid_alice",
      "member_email": "alice@example.com",
      "member_display_name": "Alice Chen",
      "role": "admin",
      "permission_overrides": [],
      "status": "active",
      "auto_assign_enabled": true,
      "created_by": "owner_uid_123",
      "created_at": "2026-05-01T10:00:00.000Z",
      "updated_at": "2026-06-02T09:15:00.000Z"
    }
  ],
  "seat_limit": 5,
  "seats_used": 3
}
```

`seat_limit` es `null` cuando su plan no tiene límite de plazas. `seats_used` cuenta solo a los miembros **activos**; suspender o eliminar a alguien libera su plaza inmediatamente.

---

## Añadir un miembro del equipo directamente

`POST /team/members`

Incorpora a alguien a su equipo de inmediato, sin necesidad de una invitación.

> **Esto no envía ningún correo electrónico.** Nadie recibe aviso de que ha sido añadido y, si aún no tenían un inicio de sesión en <span data-t="appName">Your AI Connector</span>, la cuenta creada para ellos **no tiene contraseña**, por lo que no podrán iniciar sesión hasta que la restablezcan. Utilice [Enviar una invitación](#send-an-invitation) a menos que tenga su propia forma de informar a la persona y ayudarla a iniciar sesión.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `email` | Sí | La dirección de correo electrónico del miembro del equipo. |
| `display_name` | Sí | El nombre que se muestra para ellos en la aplicación. |
| `role` | Sí | `admin`, `editor` o `viewer`. |
| `permission_overrides` | No | Excepciones por área a los valores predeterminados del rol. |
| `contact_scope` | No | `all` o `assigned` — consulte [Limitar lo que puede ver un miembro](#limiting-what-a-member-can-see). |
| `contact_scope_unassigned` | No | Con `assigned`, también permite que vean contactos que aún no tienen propietario. |
| `contact_scope_axes` | No | Limítelos a agentes, canales o departamentos específicos. |
| `sub_account_access` | No | Solo agencias: qué subcuentas de cliente pueden abrir. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/members" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "sam@example.com",
    "display_name": "Sam Rivera",
    "role": "editor"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/team/members", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${idToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "sam@example.com",
    display_name: "Sam Rivera",
    role: "editor",
  }),
});
const { member_uid } = await res.json();
```

**Respuesta** — `201 Created`

```json
{
  "success": true,
  "team_member_id": "owner_uid_123_uid_sam",
  "member_uid": "uid_sam",
  "message": "Team member created successfully."
}
```

| Estado | Cuándo |
|---|---|
| `400` | Falta `email`, `display_name` o `role`, el rol no es uno de los tres, o intentó añadirse a sí mismo. |
| `403` | No tiene permiso para gestionar el equipo, o intentó otorgar un acceso superior al suyo. |
| `409` | Esa persona ya es un miembro activo de su equipo. |
| `429` | Las plazas de equipo de su plan están llenas. |

Añadir a alguien que fue **suspendido o eliminado** anteriormente los restablece en lugar de fallar.

---

## Actualizar un miembro del equipo

`PATCH /team/members/{memberUid}`

Cambia el rol, los permisos, la visibilidad, el acceso a clientes o la participación en la asignación automática de contactos de un miembro. Envíe solo los campos que desea cambiar; todo lo que omita mantendrá su valor actual.

**Campos de la solicitud**

| Campo | Descripción |
|---|---|
| `role` | `admin`, `editor` o `viewer`. |
| `permission_overrides` | Reemplaza toda su lista de excepciones. Envíe `[]` para devolverlos a los valores predeterminados del rol. |
| `status` | Solo se acepta `active` para reactivar a un miembro suspendido. Para suspender a alguien, utilice el [endpoint de suspensión](#suspend-a-team-member). |
| `auto_assign_enabled` | `true` o `false`. |
| `contact_scope` | `all` o `assigned`. |
| `contact_scope_unassigned` | `true` o `false`. |
| `contact_scope_axes` | Consulte [Limitar lo que puede ver un miembro](#limiting-what-a-member-can-see). |
| `sub_account_access` | Solo agencias. |

> **Este es el único endpoint donde `null` significa "borrar".** Enviar `"contact_scope": null`, `"contact_scope_axes": null` o `"sub_account_access": null` elimina ese límite por completo y hace que el miembro vuelva a verlo todo. Al crear e invitar, `null` simplemente significa "no suministrado".

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/team/members/uid_sam" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "admin",
    "permission_overrides": [{ "area": "billing", "level": "none" }]
  }'
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team member updated successfully."
}
```

| Estado | Cuándo |
|---|---|
| `400` | Un valor `status` o `auto_assign_enabled` no válido, o intentó reactivar a un miembro que fue eliminado (los miembros eliminados deben ser invitados de nuevo). |
| `403` | No tiene permiso, o el cambio editaría o crearía un acceso más amplio que el suyo. |
| `404` | No existe tal miembro del equipo. |

---

## Suspender a un miembro del equipo

`POST /team/members/{memberUid}/suspend`

Suspende a alguien: mantienen su lugar en el equipo pero pierden el acceso. Utilice esto en lugar de eliminar cuando la pausa sea temporal; devuélvalos con `PATCH /team/members/{memberUid}` y `{"status": "active"}`.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/members/uid_sam/suspend" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team member suspended successfully."
}
```

Un miembro suspendido **libera su plaza**, por lo que puede invitar a otra persona en su lugar. Su acceso finaliza cuando su token de sesión actual se actualice, lo que puede tardar hasta una hora; elimínelos si necesita que sea inmediato.

| Estado | Cuándo |
|---|---|
| `400` | Intentó suspender al propietario de la cuenta, o a un miembro que ya está suspendido o eliminado. |
| `403` | Su acceso es más amplio que el suyo. |
| `404` | No existe tal miembro del equipo. |

---

## Eliminar a un miembro del equipo

`DELETE /team/members/{memberUid}`

Elimina a alguien de tu equipo y libera su puesto. Se cierra su sesión y pierde el acceso a tu cuenta; su propio inicio de sesión permanece intacto.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/team/members/uid_sam" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team member removed successfully."
}
```

La eliminación es permanente por tu parte: un miembro eliminado **no puede ser reactivado** con el endpoint de actualización; invítalo de nuevo si cambias de opinión. Su correo electrónico también se elimina de la lista de notificaciones de tu cuenta.

| Estado | Cuándo |
|---|---|
| `400` | Intentaste eliminar al propietario de la cuenta. |
| `403` | Su acceso es más amplio que el tuyo. |
| `404` | No existe tal miembro del equipo. |

---

## Limitar lo que un miembro puede ver

Tres campos opcionales, aceptados en [agregar](#add-a-team-member-directly), [actualizar](#update-a-team-member) e [invitar](#send-an-invitation), deciden cuánto de la cuenta puede ver una persona. Se acumulan: un miembro limitado en más de uno está limitado por todos ellos.

**`contact_scope`** — `all` (el valor predeterminado: todos los contactos y conversaciones) o `assigned` (solo los que tienen asignados). Con `assigned`, añade `"contact_scope_unassigned": true` para permitirles también ver los contactos que aún no tienen propietario.

**`contact_scope_axes`** — los limita a agentes, canales o departamentos específicos:

| Campo | Tipo | Descripción |
|---|---|---|
| `agents` | string[] | IDs de agente. Solo ven los chats dirigidos a uno de estos agentes. Máx. 200. |
| `channels` | string[] | Nombres de canal — `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `facebook`, `chat_widget`, `telegram`, `line`, `viber`, `tiktok`, `imessage`, `email`, `linkedin`, `skool`, `custom`, `custom_channel`. Máx. 200. |
| `departments` | string[] | IDs de departamento (consulta [Departamentos](#departments)). Solo ven los leads registrados bajo ellos. Máx. 200. |
| `include_unrouted` | boolean | Con `agents` activado, también muestra los chats que ningún agente gestiona. Desactivado por defecto. Se ignora cuando `agents` está vacío. |
| `include_undepartmented` | boolean | Con `departments` activado, también muestra los chats que no están en ningún departamento. Desactivado por defecto. Se ignora cuando `departments` está vacío. |

Los IDs de agente y departamento no se verifican al guardarlos; un ID que no existe simplemente no coincide con nada, lo que se muestra como una bandeja de entrada vacía en lugar de un error. Los nombres de canal **sí** se verifican: uno no reconocido se rechaza con `400`.


Ninguno de estos tres puede configurarse para el propietario de la cuenta; esa solicitud se rechaza con `400`.

---

## Listar invitaciones

`GET /team/invites`

Las invitaciones que has enviado, de la más reciente a la más antigua, para que puedas ver quién aún no ha aceptado.

**Parámetros de consulta**

| Parámetro | Requerido | Descripción |
|---|---|---|
| `status` | No | Solo devuelve invitaciones en este estado: `pending`, `accepted`, `declined`, `cancelled` o `expired`. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/team/invites?status=pending" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta**

```json
{
  "success": true,
  "invites": [
    {
      "id": "inv_abc123",
      "account_owner_uid": "owner_uid_123",
      "account_owner_display_name": "Acme Ltd",
      "invitee_email": "sam@example.com",
      "invitee_uid": null,
      "role": "editor",
      "permission_overrides": [],
      "status": "pending",
      "created_by": "owner_uid_123",
      "created_at": "2026-06-10T12:00:00.000Z",
      "expires_at": "2026-06-17T12:00:00.000Z",
      "responded_at": null
    }
  ]
}
```

El token de invitación nunca se devuelve; solo existe en el correo electrónico que se envió.

---

## Enviar una invitación

`POST /team/invites`

Envía por correo electrónico una invitación a alguien para que se una a tu equipo. Esta es la forma habitual de añadir a un compañero de equipo: hacen clic en el enlace, inician sesión con su propia cuenta y aceptan. Si aún no tienen una cuenta de <span data-t="appName">Your AI Connector</span>, se crea una para ellos y el correo electrónico los guía para establecer una contraseña.

**Campos de la solicitud**

| Campo | Requerido | Descripción |
|---|---|---|
| `email` | Sí | A dónde enviar la invitación. |
| `role` | Sí | `admin`, `editor` o `viewer`. |
| `permission_overrides` | No | Excepciones por área, aplicadas en el momento en que aceptan. |
| `contact_scope` | No | Aplicado cuando aceptan. |
| `contact_scope_unassigned` | No | Aplicado cuando aceptan. |
| `contact_scope_axes` | No | Aplicado cuando aceptan. |
| `sub_account_access` | No | Solo agencias. Aplicado cuando aceptan. |

Configurar los permisos por adelantado significa que no tienes que editar al miembro después; todo se copia en su membresía cuando aceptan.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/invites" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "sam@example.com", "role": "editor" }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/team/invites", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${idToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email: "sam@example.com", role: "editor" }),
});
const { invite_id } = await res.json();
```

**Respuesta** — `201 Created`

```json
{
  "success": true,
  "invite_id": "inv_abc123",
  "message": "Team invite sent successfully."
}
```

**Cosas a tener en cuenta**

- **Las invitaciones caducan después de 7 días.** Una invitación caducada puede volver a enviarse, lo que inicia un nuevo periodo de 7 días.
- **Las invitaciones pendientes ocupan un asiento.** A diferencia de añadir a un miembro directamente, el recuento de asientos aquí incluye a los miembros activos *más* las invitaciones pendientes, por lo que se rechazará una cuenta con todos los asientos ocupados antes de que se envíe el correo electrónico.
- **20 invitaciones por día**, contadas por cuenta tanto al enviar como al reenviar.

| Estado | Cuándo |
|---|---|
| `400` | Falta `email` o el rol no es válido. |
| `403` | No tienes permiso para gestionar el equipo, o intentaste otorgar un acceso superior al tuyo. |
| `409` | Ya existe una invitación pendiente para ese correo electrónico, o esa persona ya está en tu equipo. |
| `429` | Los asientos de equipo de tu plan están llenos, o has alcanzado el límite de 20 invitaciones al día. El mensaje `error` indica cuál es el caso. |

---

## Cancelar una invitación

`DELETE /team/invites/{inviteId}`

Retira una invitación antes de que sea aceptada. El enlace en el correo electrónico deja de funcionar.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/team/invites/inv_abc123" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team invite cancelled."
}
```

Tanto las invitaciones `pending` como las `expired` pueden cancelarse. Una invitación que ya fue aceptada, rechazada o cancelada devuelve `400`; una que no es tuya devuelve `403`; un ID desconocido devuelve `404`.

---

## Reenviar una invitación

`POST /team/invites/{inviteId}/resend`

Envía de nuevo el correo electrónico de invitación, para cuando se perdió o fue a la carpeta de spam. Funciona en invitaciones `pending` y `expired`, y restablece la fecha de caducidad a 7 días a partir de ahora.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/invites/inv_abc123/resend" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team invite resent successfully."
}
```

El nuevo correo electrónico contiene un nuevo enlace, y **el enlace antiguo también sigue funcionando**, por lo que una persona que encuentre el primer correo electrónico más tarde no tendrá problemas. El reenvío cuenta para el mismo límite de 20 al día que el envío, y reactivar una invitación *caducada* vuelve a comprobar tus plazas: un plan completo se rechaza con `429`.

---

## Aceptar una invitación

`POST /team/invites/accept`

Acepta una invitación con el token del correo electrónico de invitación, uniendo a la persona que ha iniciado sesión al equipo de esa cuenta.

> **Este es un acto de tu propia identidad.** Inicia sesión como tú mismo; se rechaza deliberadamente con `403` mientras trabajas dentro de la cuenta de otra persona.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `invite_token` | Sí | El token del enlace del correo electrónico de invitación. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/invites/accept" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "invite_token": "1f4c…" }'
```

**Respuesta**

```json
{
  "success": true,
  "team_member_id": "owner_uid_123_uid_sam",
  "account_owner_uid": "owner_uid_123",
  "message": "Team invite accepted successfully."
}
```

| Estado | Cuándo |
|---|---|
| `400` | Falta `invite_token`, o la invitación es para tu propia cuenta. |
| `403` | La sesión está trabajando dentro de otra cuenta, o la invitación se envió a una dirección de correo electrónico diferente a la que utilizas para iniciar sesión. |
| `404` | La invitación no existe o ya se ha utilizado. |
| `429` | Las plazas de la cuenta se llenaron entre la invitación y tu aceptación. |
| `504` | La invitación ha caducado. Pide al remitente que la reenvíe. |

---

## Rechazar una invitación

`POST /team/invites/decline`

Rechaza una invitación con el token del correo electrónico. Al igual que al aceptar, este es un acto de tu propia identidad y se rechaza mientras trabajas dentro de otra cuenta.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/invites/decline" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "invite_token": "1f4c…" }'
```

**Respuesta**

```json
{
  "success": true,
  "message": "Team invite declined."
}
```

---

## Departamentos

Un **departamento** es un grupo con nombre dentro de tu equipo: Ventas, Atención al cliente, RR. HH. Asigna un equipo responsable a un cliente potencial, puede reclamar nuevas conversaciones por sí mismo y se puede utilizar para limitar lo que ve un miembro.

> **Estos cuatro endpoints requieren una clave API.** A diferencia del resto de esta página, se autentican como cualquier otro endpoint de la API (consulta [Autenticación](authentication.md)). Una sesión iniciada también funciona: la lectura requiere `contacts` en `view`, y la creación, modificación o eliminación requiere `team_management` en `edit`.

**El objeto departamento**

| Campo | Tipo | Descripción |
|---|---|---|
| `id` | string | El ID del departamento. Úsalo en `contact_scope_axes.departments` y en las rutas a continuación. |
| `name` | string | Cómo se llama el equipo. Hasta 60 caracteres, único en la cuenta. |
| `color` | string \| null | Color de acento como `#rrggbb`, o `null`. |
| `member_uids` | string[] | Los miembros del equipo en este departamento. Puede incluir al propietario de la cuenta. |
| `auto_assign_enabled` | boolean | Si un cliente potencial registrado en este departamento también se asigna a alguien del mismo. `false` significa que el departamento trabaja desde una cola compartida. |
| `routing_agents` | string[] | Las nuevas conversaciones gestionadas por estos agentes de IA se registran automáticamente en este departamento. Vacío significa que no hay regla de agente. |
| `routing_channels` | string[] | Las nuevas conversaciones en estos canales se registran aquí automáticamente. Vacío significa que no hay regla de canal. |
| `created_by` | string \| null | Quién lo creó. |

Cuando se establecen tanto `routing_agents` como `routing_channels`, una conversación debe coincidir con **ambos** para registrarse aquí; así es como le das a un equipo "el agente de soporte, pero solo en WhatsApp".

Una cuenta puede tener hasta **50** departamentos.

### Listar departamentos

`GET /team/departments`

```bash
curl "https://api.youraiconnector.com/v1/team/departments?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "departments": [
    {
      "id": "dep_abc123",
      "name": "Sales",
      "color": "#2f6fed",
      "member_uids": ["uid_alice", "uid_bob"],
      "auto_assign_enabled": true,
      "routing_agents": [],
      "routing_channels": ["whatsapp"],
      "created_by": "owner_uid_123"
    }
  ]
}
```

### Crear un departamento

`POST /team/departments`

**Campos de la solicitud**

| Campo | Requerido | Descripción |
|---|---|---|
| `name` | Sí | Hasta 60 caracteres. No debe coincidir con un departamento existente. |
| `color` | No | `#rrggbb` hex, o `null`. |
| `member_uids` | No | Quién forma parte de él. Cada UID debe ser el propietario de la cuenta o un miembro del equipo **activo**. |
| `auto_assign_enabled` | No | Por defecto es `true`. |
| `routing_agents` | No | IDs de agentes cuyos nuevos chats llegan aquí. |
| `routing_channels` | No | Nombres de canales cuyos nuevos chats llegan aquí: mismo vocabulario que `contact_scope_axes.channels`. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/team/departments?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sales",
    "color": "#2f6fed",
    "member_uids": ["uid_alice", "uid_bob"],
    "routing_channels": ["whatsapp"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/team/departments", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Sales",
    color: "#2f6fed",
    member_uids: ["uid_alice", "uid_bob"],
    routing_channels: ["whatsapp"],
  }),
});
const { department } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/team/departments",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "Sales",
        "color": "#2f6fed",
        "member_uids": ["uid_alice", "uid_bob"],
        "routing_channels": ["whatsapp"],
    },
)
department = res.json()["department"]
```

**Respuesta** — `201 Created`

```json
{
  "success": true,
  "department": {
    "id": "dep_abc123",
    "name": "Sales",
    "color": "#2f6fed",
    "member_uids": ["uid_alice", "uid_bob"],
    "auto_assign_enabled": true,
    "routing_agents": [],
    "routing_channels": ["whatsapp"],
    "created_by": "owner_uid_123"
  }
}
```

| Estado | Cuándo |
|---|---|
| `400` | `name` falta o es demasiado largo, `color` no es `#rrggbb`, un nombre de canal no se reconoce, un UID listado no es un miembro activo de este equipo, o ya tienes 50 departamentos. |
| `409` | Ya existe un departamento con ese nombre. |

### Actualizar un departamento

`PATCH /team/departments/{departmentId}`

Modifica un departamento. Solo se cambian los campos que envíes.

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/team/departments/dep_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "member_uids": ["uid_alice"], "auto_assign_enabled": false }'
```

**Respuesta**

```json
{
  "success": true,
  "department": {
    "id": "dep_abc123",
    "name": "Sales",
    "color": "#2f6fed",
    "member_uids": ["uid_alice"],
    "auto_assign_enabled": false,
    "routing_agents": [],
    "routing_channels": ["whatsapp"],
    "created_by": "owner_uid_123"
  }
}
```

Si no se envían campos reconocidos, se devuelve `400`; un departamento desconocido devuelve `404`; un nombre que entra en conflicto con otro departamento devuelve `409`.

### Eliminar un departamento

`DELETE /team/departments/{departmentId}`

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/team/departments/dep_abc123?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "deleted": "dep_abc123"
}
```

> **Se rechaza la eliminación de un departamento al que alguien está limitado.** La respuesta `400` indica los miembros cuya visibilidad está restringida a dicho departamento, para que pueda cambiar su alcance primero. Esto es deliberado: eliminar la limitación de forma silenciosa les daría acceso a toda su base de clientes sin que quedara constancia de ello.

Los contactos archivados bajo un departamento eliminado no se modifican; simplemente dejan de mostrar un departamento, y la próxima vez que los archive, se guardarán correctamente.

---

## Comprobar sus propios permisos

`GET /team/permissions`

Devuelve lo que la persona que ha iniciado sesión tiene permitido hacer en la cuenta en la que está trabajando actualmente. Úselo para ocultar botones que un miembro no puede utilizar, en lugar de dejar que descubran la limitación a través de un error.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/team/permissions" \
  -H "Authorization: Bearer FIREBASE_ID_TOKEN"
```

**Respuesta: el propietario de la cuenta**

```json
{
  "success": true,
  "role": "owner",
  "is_team_mode": false,
  "permissions": {
    "campaigns": "full",
    "contacts": "full",
    "messages": "full",
    "appointments": "full",
    "settings": "full",
    "billing": "full",
    "team_management": "full",
    "analytics": "full",
    "phone_numbers": "full",
    "integrations": "full",
    "faqs": "full",
    "daily_summaries": "full"
  }
}
```

**Respuesta: un miembro del equipo trabajando dentro de una cuenta**

```json
{
  "success": true,
  "role": "editor",
  "is_team_mode": true,
  "permissions": { "campaigns": "edit", "billing": "none", "…": "…" },
  "member": {
    "uid": "uid_sam",
    "email": "sam@example.com",
    "display_name": "Sam Rivera",
    "account_owner_uid": "owner_uid_123"
  }
}
```

`role` es `owner` cuando la persona que ha iniciado sesión es el propietario de la cuenta; de lo contrario, es su rol en el equipo. `member` solo está presente en el modo de equipo y contiene `contact_scope`, `contact_scope_unassigned` y `contact_scope_axes` cuando su membresía los incluye.

---

## Tokens de sesión

Cinco endpoints generan un token de inicio de sesión de un solo uso para cambiar entre cuentas. Todos responden de la misma manera:

```json
{
  "success": true,
  "customToken": "eyJhbGciOi…"
}
```

El token se intercambia por una sesión con el SDK de cliente de Firebase. **No es una clave de API y no puede enviarse como tal**, por lo que estos endpoints solo son útiles dentro de una aplicación propia.

| Endpoint | Qué hace | Cuerpo |
|---|---|---|
| `POST /team/tokens/team-member` | Permite que un miembro del equipo comience a trabajar dentro de una cuenta a la que pertenece. | `account_owner_uid` (obligatorio) |
| `POST /team/tokens/return-from-team` | Los devuelve a su propia cuenta. | — |
| `POST /team/tokens/assist` | Permite al personal de <span data-t="appName">Your AI Connector</span> abrir la cuenta de un cliente para ayudar. Solo para el personal. | `customerUid` |
| `POST /team/tokens/return-to-admin` | Finaliza una sesión de asistencia y devuelve al personal a su propia cuenta. | — |
| `POST /team/tokens/agency-assist` | Permite que una agencia abra una de sus subcuentas de cliente o, si se llama sin una, volver a la cuenta de la agencia. | `subAccountUid` (opcional) |

Cada uno rechaza con `403` cuando la sesión no tiene derecho a ello: no es miembro de esa cuenta, no es personal, esa subcuenta no pertenece a su agencia o no se le ha concedido, o la sesión no se encuentra actualmente en el modo en que termina el endpoint.

---

## Asignar un rol de plataforma

`POST /team/users/{targetUid}/role`

Establece el rol de **plataforma** de un usuario: `User`, `Dev`, `Support` o `Agency`. Esto no es la pertenencia a un equipo: es el tipo de cuenta <span data-t="appName">Your AI Connector</span> que tiene alguien.

Este endpoint está restringido al personal de <span data-t="appName">Your AI Connector</span>, y el último `Dev` restante no puede ser degradado. Se enumera por completitud; no forma parte de la gestión de su propio equipo.

```json
{
  "success": true,
  "targetUid": "uid_sam",
  "role": "Agency",
  "claimUpdated": true
}
```

| Estado | Cuándo |
|---|---|
| `400` | `role` falta o no es uno de los cuatro, o esto eliminaría al último `Dev`. |
| `403` | Usted no es personal, o la sesión está trabajando dentro de otra cuenta. |
| `404` | No existe tal usuario. |

---

## Errores de la API de equipos

Los endpoints de equipos devuelven el sobre de error estándar, siempre con `error_code` junto al estado HTTP:

```json
{
  "success": false,
  "error_code": 403,
  "error": "Cannot grant \"full\" access to \"billing\" — exceeds your own permissions."
}
```

| Estado | Cuándo ocurre en un endpoint de equipo |
|---|---|
| `400` | Falta un campo obligatorio o no es válido, o la acción no está permitida en este estado (reactivar a un miembro eliminado, suspender al propietario, eliminar un departamento al que alguien está limitado). |
| `401` | Envió una clave de API a un endpoint que requiere una persona con sesión iniciada; consulte [Autenticación](#authentication-these-endpoints-need-a-signed-in-person). |
| `403` | No tiene el permiso `team_management`, el cambio excede su propio acceso, o la acción es rechazada mientras trabaja dentro de otra cuenta. |
| `404` | No existe tal miembro, invitación, departamento o usuario. |
| `409` | Ya es miembro del equipo, ya existe una invitación pendiente, o ya existe un departamento con ese nombre. |
| `429` | Los puestos del equipo están llenos, se alcanzó el límite de 20 invitaciones al día, o alcanzó el límite de tasa de la API. |
| `504` | La invitación que intentó aceptar ha caducado. |

Los códigos compartidos que puede devolver cualquier endpoint — `429` (límite de tasa) y `500` — se enumeran con orientación sobre reintentos en [Errores y paginación](errors-and-pagination.md).

---

## Relacionado

- [Gestión de equipos](../settings/team-management.md) — las mismas funciones en el panel de control, con capturas de pantalla.
- [Autenticación](authentication.md) — cómo enviar un token de ID de Firebase en lugar de una clave de API.
- [API de contactos](contacts.md) — los contactos a los que se aplican los límites de visibilidad de un miembro.

