> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.crehana.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.crehana.com/_mcp/server.

# Asignar turnos

Si tu organización usa Asistencia en Crehana y la planificación de turnos vive
en otro sistema, podés mandar las asignaciones por API en vez de cargarlas a
mano. Una asignación hecha por API es igual a una hecha desde Crehana: a partir
de esas fechas las marcas de la persona se comparan contra ese turno, la
puntualidad se clasifica con sus tolerancias y la gestión diaria se recalcula.

```
POST /api/v5/rest/org/{organization_slug}/time/attendance/shift-assignments/
```

La referencia completa está en
[Assign Shift](/api-reference/endpoints/attendance/assign-shift).

## Antes de empezar

* **Credenciales de administrador.** Las credenciales de la API tienen que ser
  de una cuenta ADMIN de la organización. Con otras, la respuesta es `403`.
* **El turno ya existe en Crehana.** Cada turno tiene un id (`shift_id`) y, si
  es rotativo, sus ciclos también tienen id (`start_cycle_id`). Si el turno lo
  creás por API con [Crear turnos](/documentacion/casos-de-uso/crear-turnos),
  la respuesta trae los dos ids: guardalos en tu sistema. Si el turno se creó
  desde Crehana, hoy la API no lo lista: pedile los ids a tu contacto de
  Crehana.
* **El id de cada persona.** `user_ids` lleva ids centralizados: el `user.id` de
  [`GET /org/{organization_slug}/users/user-organizations/`](/api-reference). No
  es el id de learning (`learning_user_id`). Todas las personas tienen que
  pertenecer a la organización: si alguna no, la respuesta es `404` con los ids
  que fallaron y no se asigna a nadie.

## Una asignación de punta a punta

```bash
curl -X POST "https://www.crehana.com/api/v5/rest/org/acme/time/attendance/shift-assignments/" \
  -H "Api-Key: $API_KEY" \
  -H "Secret-Access: $SECRET_ACCESS" \
  -H "Content-Type: application/json" \
  -d '{
    "shift_id": "4ed0f6c7-a2a7-4de7-90cd-ae8cf26aabb1",
    "user_ids": [97097, 97099],
    "start_date": "2026-10-13",
    "end_date": "2026-12-31"
  }'
```

```json
{
  "success": true,
  "message": "Shift assigned.",
  "code": "attendance-003",
  "datetime": "2026-10-07T09:00:03.120394",
  "data": {
    "shift_id": "4ed0f6c7-a2a7-4de7-90cd-ae8cf26aabb1",
    "start_date": "2026-10-13",
    "end_date": "2026-12-31",
    "start_cycle_id": null,
    "assignments": [
      {
        "id": "94caeb90-fd35-4e8b-9e67-28c8377cdbfc",
        "user_id": 97097,
        "shift_id": "4ed0f6c7-a2a7-4de7-90cd-ae8cf26aabb1",
        "start_date": "2026-10-13",
        "end_date": "2026-12-31",
        "start_cycle_id": null,
        "is_active": true,
        "created_at": "2026-10-07T14:00:03"
      },
      { "id": "…", "user_id": 97099, "…": "…" }
    ],
    "already_assigned_user_ids": []
  }
}
```

El `201` confirma que las personas de `assignments` ya tienen el turno en esas
fechas.

## Los campos

### `shift_id`

El id del turno, tal como existe en Asistencia. Tiene que ser de tu
organización y estar activo; si no, la respuesta es `404`.

### `user_ids`

Entre 1 y 100 personas por llamada, sin repetir. Para más gente, hacé varias
llamadas.

La llamada es **todo o nada**: si una sola persona no pertenece a la
organización, o no puede tomar el turno (ver
[Si la persona ya tiene un turno](#si-la-persona-ya-tiene-un-turno)), no se
asigna a nadie y la respuesta dice quién falló. Para que una persona no frene
al resto, mandá una llamada por persona.

### `start_date` y `end_date`

Días calendario en formato `YYYY-MM-DD`, los dos inclusive. `end_date` es
opcional: sin él, la asignación queda abierta hasta que se cambie desde
Crehana o se asigne otro turno.

Una asignación puede empezar en el pasado. En ese caso Crehana vuelve a
calcular la asistencia de esos días (hasta 30 días hacia atrás) contra el turno
nuevo.

### `start_cycle_id`

Solo para turnos **rotativos**: el id del ciclo con el que arranca la rotación
el día `start_date`. Por ejemplo, en un turno "4 días de trabajo, 2 de
descanso", decide si la persona empieza trabajando o descansando.

* Turno rotativo sin `start_cycle_id` → `400`.
* Turno fijo con `start_cycle_id` → `400`.

## Si la persona ya tiene un turno

Hoy una persona tiene **un** turno por día. Si ya tenía un turno que se solapa
con el nuevo en esas fechas, la asignación anterior **se desactiva completa**
(también los días que quedaban fuera del período nuevo) y el turno nuevo la
reemplaza. Por eso, para mover a alguien de turno a partir de una fecha alcanza
con mandar la asignación nueva desde esa fecha; y si querés conservar parte del
turno anterior, mandá después una asignación con ese turno para los días que
quedaron sin cubrir.

> **Warning**
>
> Crehana va a permitir que una persona tenga más de un turno en el mismo día.
> Cuando eso esté activo, un turno que se solape en horario con otro que la
> persona ya tiene **no se reemplaza**: la asignación se rechaza con `409` y
> nadie recibe el turno. Preparate para ese `409` desde ahora; su forma ya está
> definida.

Un choque se informa con un detalle por persona:

```json
{
  "detail": {
    "errors": ["The shift clashes with a shift that at least one of the people already has in that period; nothing was assigned."],
    "code": "SHIFT_ASSIGNMENT_BLOCKED",
    "conflicts": [
      {
        "user_id": 97099,
        "reason": "SHIFT_TIME_OVERLAP",
        "conflict_date": "2026-10-14",
        "resulting_shift_count": 2,
        "conflicting_shift_id": "331e54df-2597-4286-8ed3-8ca132cc91d3",
        "conflicting_shift_name": "Turno Tarde"
      }
    ]
  }
}
```

| `reason`                     | Significa                                                                                                      |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `SHIFT_TIME_OVERLAP`         | El turno nuevo se solapa en horario con `conflicting_shift_name` el día `conflict_date`                        |
| `DAILY_SHIFT_LIMIT_EXCEEDED` | La persona superaría la cantidad de turnos que admite un día. En este caso `conflicting_shift_id` viene `null` |

Ante un `409`, resolvé el choque desde Crehana (quitando o acortando el turno
anterior) y volvé a mandar la asignación.

## Reintentos: mandar dos veces es seguro

Si se corta la conexión y no sabés si la asignación llegó, reenviala igual. Las
personas que **ya tenían exactamente ese turno en ese período** no se duplican:
vienen en `already_assigned_user_ids` y no en `assignments`. Si no había nada
nuevo para nadie, la respuesta es `200` con `code: attendance-004`.

```json
{
  "success": true,
  "message": "Everybody already had this shift in this period.",
  "code": "attendance-004",
  "data": { "assignments": [], "already_assigned_user_ids": [97097, 97099], "...": "..." }
}
```

Evitá reenviar en paralelo: si dos copias llegan exactamente a la vez, una se
guarda y la otra puede responder `409` (`INTEGRITY_ERROR`).

## Respuestas

| Status | Cuándo                                                                                                                                                              | Qué hacer                                                |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `201`  | Turno asignado a al menos una persona                                                                                                                               | —                                                        |
| `200`  | Todas ya tenían ese turno en ese período                                                                                                                            | Nada, ya está                                            |
| `400`  | Turno rotativo sin `start_cycle_id` (o fijo con él), o el `organization_slug` no existe                                                                             | Ver `detail.errors`. Reintentar igual no sirve           |
| `401`  | Faltan las credenciales o no son válidas                                                                                                                            | Revisar los dos headers                                  |
| `403`  | Las credenciales no administran esa organización                                                                                                                    | Usar credenciales de un admin de la organización         |
| `404`  | El turno no existe en la organización o está inactivo, o alguna persona no pertenece a ella (`detail.user_ids` dice cuáles)                                         | Revisar el `shift_id` o los `user_ids`                   |
| `409`  | Choque con un turno que alguien ya tiene, o la misma asignación llegó dos veces a la vez                                                                            | Ver `detail.conflicts`; resolver el choque desde Crehana |
| `422`  | El cuerpo no cumple el esquema (falta un campo, un id que no es UUID, fechas mal formadas, `end_date` anterior a `start_date`, más de 100 personas o ids repetidos) | Corregir el campo que indica `detail[].loc`              |
| `500`  | Error nuestro                                                                                                                                                       | Reintentar con backoff                                   |

| `detail.code`              | Status | Significa                                                                                  |
| -------------------------- | ------ | ------------------------------------------------------------------------------------------ |
| `VALIDATION_ERROR`         | `400`  | Regla del turno: falta o sobra `start_cycle_id`                                            |
| `SHIFT_NOT_FOUND`          | `404`  | El turno no es de la organización o está inactivo                                          |
| `USER_NOT_FOUND`           | `404`  | Alguna persona no pertenece a la organización; `detail.user_ids` trae los ids que fallaron |
| `SHIFT_ASSIGNMENT_BLOCKED` | `409`  | Choque de turnos; `detail.conflicts` dice con quién y por qué                              |
| `INTEGRITY_ERROR`          | `409`  | La misma asignación llegó dos veces al mismo tiempo                                        |
| `FORBIDDEN`                | `403`  | Las credenciales no administran la organización                                            |

Los textos de `errors` son para un log o para una persona: programá contra el
status y, si hace falta, contra `detail.code`. Más en
[Errores](/documentacion/conceptos/errores).

## Lo que la API no hace (todavía)

* No lista los turnos ni sus ciclos. Los ids te los da Crehana.
* No quita ni acorta asignaciones: eso se hace desde Crehana.
* No crea ni edita turnos.

## Lista de verificación

* [ ] Las credenciales son de un ADMIN de la organización.
* [ ] Tenés el `shift_id` del turno y, si es rotativo, el `start_cycle_id`.
* [ ] Cada persona pertenece a la organización y tenés su `user_id` centralizado.
* [ ] Las fechas van en `YYYY-MM-DD` y `end_date` no es anterior a `start_date`.
* [ ] Si alguien ya tenía turno en esas fechas, sabés que el anterior se desactiva completo.
* [ ] Tu integración ya contempla el `409` por choque de turnos.
* [ ] Los `500` y los cortes de red se reintentan. Los `4xx` se registran y no se reintentan.