Ir al contenido principal

Cómo utilizar la API de Plain

Integración con la API de Plain

Este artículo explica cómo integrarse con la API de Plain para:

  • Autenticarse correctamente

  • Exportar fichajes y ausencias

  • Obtener vacaciones y otras cuentas de empleados

  • Importar empleados y workflows de forma masiva (bulk)

  • Trabajar en entornos multivenue

El objetivo es que puedas entender el flujo completo antes de empezar a programar.


1. Autenticación

Antes de utilizar cualquier endpoint de Plain, es necesario autenticarse y obtener un token JWT.

Este token identifica a tu aplicación y autoriza las peticiones que hagas a la API.

1.1 Cómo obtener el token JWT

El proceso tiene dos pasos:

Paso 1: Enviar credenciales al endpoint de autenticación

POST /saas/api/auth/token/

Este endpoint requiere un header Authorization con un token dummy estático:

Authorization: Bearer 9cb029da-b5a6-429e-8184-4ebfb89a732c

Body de la petición:

{

"grant_type": "password",

"username": "<usuario>",

"password": "<password>"

}

Notas importantes:

  • El username en Plain siempre es un email.

  • El usuario debe tener rol administrador.

  • Es recomendable crear un usuario específico para la integración.

Paso 2: Usar el token devuelto

La respuesta incluirá:

{
"access_token": "<token jwt>",
"token_type": "BEARER",
"expires_in": 43199,
"company_name": "<company name>"
}

El access_token es el token JWT que deberás incluir en todas las llamadas posteriores:

Authorization: Bearer <token jwt>

El token es válido durante 12 horas. No es necesario solicitar uno nuevo en cada llamada: puedes almacenarlo y reutilizarlo hasta que expire.

1.2 Entornos multivenue

Si trabajas en un entorno multivenue:

  • La respuesta incluirá other_company_tokens.

  • Cada venue tiene su propio token JWT.

  • Debes crear el usuario administrador en cada venue.

  • El username debe ser exactamente el mismo en todas las venues.

  • Para operar en cada venue, debes usar el token correspondiente.


2. Exportación de datos a Excel

La exportación de datos (fichajes o ausencias) sigue siempre el mismo patrón:

  1. Obtener un token de descarga.

  2. Usar ese token para descargar el archivo.

Este sistema desacopla la generación del archivo de su descarga.

2.1 Exportación de fichajes

Paso 1: Obtener token de descarga

GET /saas/api/time-registrations/export?page_size=1000&start_from=2026-01-01&start_to=2026-02-28

Respuesta:

{
"access_token": "<token descarga documentos>",
"token_type": "EXPORT",
"expires_in": 31535999,
"company_name": "Nombre de empresa"
}

Este token no es el JWT de autenticación. Es un token específico para descargar el archivo generado.

Paso 2: Descargar el Excel

GET /saas/api/store/download/<token descarga documentos>

Este endpoint devuelve directamente el archivo Excel como un stream.

2.2 Exportación de ausencias

El proceso es exactamente el mismo, pero usando otro endpoint:

GET /saas/api/workflows/export?page_size=1000&workflow_type=Ausencias&sort=-requested&workflow_status=Pendiente&start_from=2026-01-01&start_to=2026-01-31

Después se descarga el archivo usando:

GET /saas/api/store/download/<token descarga documentos>

Multivenue en exportaciones

Si trabajas con multivenue:

  • Debes repetir el proceso completo para cada venue.

  • Cada venue requiere su propio JWT.

  • Cada exportación genera su propio token de descarga.


3. Obtener vacaciones y otras cuentas de empleados

Plain no expone directamente las vacaciones disponibles o disfrutadas mediante un endpoint aislado. Las cuentas se obtienen a través de una vista de planificación. Esto permite controlar qué columnas quieres exponer en la API.

3.1 Crear una vista de planificación

Desde la aplicación:

Planificación → Configurar vistas → Crear nueva vista

Añade las columnas:

  • Vacaciones pendientes periodo

  • Vacaciones disfrutadas periodo

Guarda la vista con un nombre identificable (por ejemplo: "Vista integración").

3.2 Obtener el ID de la vista

GET /saas/api/plan-views

Este endpoint devuelve un listado de vistas con su id y name. Busca la vista creada y guarda su id.

3.3 Obtener las cuentas mediante API

Con el id de la vista:

GET /api/accounts/assignments?page_size=1000&start=2026-01-01&end=2026-01-31&group_id=566&plan_view_id=<id obtenido>

La respuesta contiene un array data. Para cada empleado:

  • El email está en: data[i].employee.email

  • Las cuentas están en: data[i].counters

Cada elemento de counters contiene:

  • label → nombre de la cuenta

  • value → valor de la cuenta

Los valores devueltos corresponden exactamente a las columnas configuradas en la vista.


4. Importación masiva de datos (bulk imports)

Además de exportar y consultar datos, la API de Plain permite importar empleados y workflows de forma masiva. Estos endpoints aceptan un array de elementos en una sola petición y devuelven el resultado individual de cada uno.

4.1 Consideraciones generales

Aplican a todos los endpoints de importación:

  • Autenticación: requieren el token JWT obtenido en la sección 1, enviado en el header Authorization: Bearer <token jwt>.

  • Método y formato: son peticiones POST con Content-Type: application/json. El body es siempre un array JSON de elementos.

  • Comportamiento upsert: cada endpoint tiene un identificador. Si ya existe un registro con ese identificador, su información se actualiza; si no existe, se crea uno nuevo.

    • Empleados → el identificador es el email.

    • Workflows → el identificador es la combinación de employee_code + fecha start + shift.

  • Respuesta: la API devuelve un array con un resultado por cada elemento enviado. Cada resultado es un objeto de éxito (StatusDTO) o de error (ErrorDTO). En caso de error, el campo index indica la posición del elemento dentro del array enviado.

4.2 Importar empleados

POST /api/employees/imports/bulk

El identificador del empleado es el email. Si ya existe un empleado con ese email, se actualizan sus datos; en caso contrario, se crea uno nuevo.

Campos de cada elemento (EmployeeBulkDTO):

Campo

Tipo

Descripción

name

string

Nombre del empleado

surname

string

Apellidos del empleado

email

string

Email del empleado. Es el identificador usado para crear o actualizar

birthday

string (fecha)

Fecha de nacimiento. ISO-8601 sin hora ni offset (p. ej. 1980-08-28)

start_day

string (fecha)

Fecha de alta. Formato 2019-08-28

end_day

string (fecha)

Fecha de baja. Formato 2019-08-28

enabled

boolean

Indica si el empleado está activo

with_access

boolean

Indica si el empleado tiene acceso a la aplicación

do_not_receive_notifications

boolean

Si es true, el empleado no recibe notificaciones

exact_daily_hours_by_contract

boolean

Usar las horas diarias exactas definidas por el contrato

phone

string

Teléfono. En formato internacional si es posible

position

string

Puesto del empleado

code

string

Código opcional que identifica al empleado en la empresa

nif

string

NIF del empleado (opcional)

card_code

string

Código hexadecimal opcional de la tarjeta del empleado

plan_order

integer

Orden de aparición del empleado en la planificación

group

string

Grupo del empleado (opcional)

group_start

string (fecha)

Fecha de inicio en el grupo. Formato 2019-08-28

contract

string

Contrato del empleado (opcional)

contract_start

string (fecha)

Fecha de inicio del contrato. Formato 2019-08-28

function

string

Función del empleado (opcional)

shift_pattern

string

Patrón de turnos del empleado (opcional)

shift_pattern_start

string (fecha)

Fecha de inicio del patrón de turnos. Formato 2019-08-28

Ejemplo de body:

[
{
"name": "María",
"surname": "García López",
"email": "[email protected]",
"code": "EMP-001",
"enabled": true,
"with_access": true,
"start_day": "2024-01-15",
"position": "Camarera",
"group": "Sala"
},
{
"name": "Juan",
"surname": "Pérez Ruiz",
"email": "[email protected]",
"code": "EMP-002",
"enabled": true,
"with_access": false,
"start_day": "2023-06-01"
}
]

4.3 Importar workflows

POST /api/workflows/imports/bulk

Un workflow representa una asignación planificada para un empleado durante un periodo, con un turno asociado. El identificador es la combinación de employee_code, la fecha start y el shift. Si ya existe un workflow con esa combinación, se actualiza; en caso contrario, se crea uno nuevo.

Campos de cada elemento (WorkflowBulkDTO):

Campo

Tipo

Descripción

start

string (date)

Inicio del workflow. fecha en formato ISO-8601 fe (p. ej. 2019-08-28). Parte del identificador

end

string (date)

Fin del workflow. Mismo formato que start. Opcional. Únicamente para workflows de más de un día.

comments

string

Comentarios de la solicitud

answer_comments

string

Comentarios de la respuesta

shift

string

Turno a planificar durante la duración del workflow. Obligatorio. Parte del identificador

employee_code

string

Código que identifica al empleado en la empresa. Parte del identificador

status

string

Permite cancelar workflows cuando recibe el valor "Anulado". Opcional. Si no es una cancelación, no enviar status.

workflow_hourly_requested

booleano

true, cuando se trata de una ausencia por horas. Opcional. Si no se envía, por defecto es false.

start_hour

string

Hora de inicio de la ausencia por horas. ISO-8601 con offset de zona horaria (p. ej. 2019-08-28T10:30:00+02:00, o 2019-08-28T10:30:00Z para UTC). Opcional. Únicamente para las ausencias por horas

duration

float

Duración de la ausencia por horas. (p. ej. 3.5) Opcional. Únicamente para las ausencias por horas

Ejemplo de body:

[
{
"employee_code": "EMP-001",
"start": "2026-03-01"
"shift": "V",
"comments": "Asignación de Vacaciones de un día creada por integración"
},

{
"employee_code": "EMP-001",
"start": "2026-05-01",
"end": "2026-05-05",
"shift": "V",
"comments": "Asignación de vacaciones de varios días para anular"

"status": "Anulado"
},

{
"employee_code": "EMP-001",
"start": "2026-03-01",
"shift": "V",
"comments": "Asignación de vacaciones de ausencia por horas",

"workflow_hourly_requested": true,

"start_hour": "2026-08-10T10:30:00Z",

"duration": 4.5
}

]

4.4 Importar fichajes

POST /api/time-registrations/imports/bulk

Un fichaje (time registration) representa un registro de tiempo de un empleado en un día concreto, con una hora de inicio, una hora de fin opcional y un tipo de tiempo asociado. El identificador de un fichaje es la combinación de empleado (employee_code) , día (day) y hora de inicio (start).

  • Si en el array items se envía un fichaje cuyo start todavía no existe para ese empleado y día, se crea.

  • Si ya existe un fichaje con ese start, se actualiza (tipo de tiempo y hora de fin).

  • Si un fichaje que existe en la base de datos para ese empleado y día no aparece en el array items, se considera eliminado y se borra.

Por este motivo, para un empleado y un día debes enviar siempre todos los fichajes de ese día, incluso los que no han cambiado. Si omites alguno, se interpretará como que se ha eliminado.

Esto no significa que tengas que enviar los fichajes de todos los días: solo debes incluir los días en los que haya fichajes añadidos, modificados o eliminados. Cada elemento del array representa el conjunto completo de fichajes de un empleado en un día.

Campos de cada elemento (TimeRegistrationBulkDTO):

Campo

Tipo

Descripción

day

string (fecha)

Día del fichaje. ISO-8601 sin hora ni offset (p. ej. 2019-08-28). Parte del identificador

employee_code

string

Código que identifica al empleado en la empresa. Obligatorio. Parte del identificador. Debe corresponder a un empleado existente

items

array

Conjunto completo de fichajes del empleado en ese día (ver TimeRegistrationItemBulkDTO)

Campos de cada fichaje dentro de items (TimeRegistrationItemBulkDTO):

Campo

Tipo

Descripción

start

string (date-time)

Inicio del fichaje. ISO-8601 con offset de zona horaria (p. ej. 2019-08-28T10:30:00+02:00, o 2019-08-28T10:30:00Z para UTC). Parte del identificador. Cuando el servidor devuelve esta información, la zona es UTC y se expresa sin offset (p. ej. 2019-08-28T10:30:00)

end

string (date-time)

Fin del fichaje. Mismo formato que start. Opcional.

time_type

string

Nombre del tipo de tiempo del fichaje. Debe corresponder a un tipo de tiempo existente en la empresa

Ejemplo de body:

[
{
"day": "2026-06-25",
"employee_code": "47",
"items": [
{
"start": "2026-06-25T07:30:12Z",
"end": "2026-06-25T11:45:15Z",
"time_type": "Trabajo"
},
{
"start": "2026-06-25T13:30:12Z",
"end": "",
"time_type": "Trabajo"
}
]
}
]

4.5 Formato de respuesta y manejo de errores

Los endpoints de importación devuelven un array con un resultado por cada elemento enviado, en el mismo orden.

  • Resultado correcto (StatusDTO): contiene el campo status (entero) con el código del resultado.

  • Resultado con error (ErrorDTO): contiene status (entero) y un array errors con el detalle de cada problema (ErrorItemDTO).

Campos de ErrorItemDTO:

Campo

Tipo

Descripción

code

string

Código del error

key

string

Clave asociada al error

fields

array de string

Campos afectados por el error

value

string

Valor que ha provocado el error

availables

string

Valores válidos o disponibles, cuando aplica

description

string

Descripción legible del error

index

integer

Posición del elemento dentro del array enviado

Ejemplo ilustrativo de respuesta (un elemento correcto y otro con error):

[
{ "status": 200 },
{
"status": 400,
"errors": [
{
"code": "...",
"key": "email",
"fields": ["email"],
"value": "juan.perez",
"availables": "...",
"description": "...",
"index": 1
}
]
}
]

Conviene recorrer siempre el array de respuesta y comprobar cada resultado: una petición puede importar correctamente unos elementos y fallar en otros. El campo index permite identificar exactamente qué elemento del array original ha fallado.

4.6 Multivenue en importaciones

Al igual que en las exportaciones, en un entorno multivenue debes repetir el proceso completo para cada venue, usando el token JWT correspondiente a cada una.


5. Traspaso de la bolsa de horas

Esta sección describe cómo sincronizar el balance de horas (bolsa de horas) de un sistema externo con Plain. El flujo consiste en consultar el balance actual de cada empleado en Plain, calcular la diferencia respecto al balance del sistema externo, y enviar una transacción de compensación que ajuste el balance al valor correcto.

Para evitar acumular transacciones de compensación en sincronizaciones sucesivas, se recomienda guardar el id de la transacción creada para cada empleado y, en las siguientes ejecuciones, actualizarla en vez de crear una nueva.

5.1 Visión general del flujo

  1. Obtener el balance actual de cada empleado en Plain mediante el endpoint de cuentas (/saas/api/accounts/assignments), paginando con page_size=50.

  2. Identificar al empleado por code o email (lo que utilice el sistema externo) y guardar también el id del empleado de Plain.

  3. Calcular la diferencia: compensación = balance_externo − balance_plain.

  4. Si no existe transacción previa para ese empleado, crear una nueva mediante POST /saas/api/transactions/.

  5. Si ya existe transacción previa (porque se sincronizó antes), actualizarla mediante PUT /saas/api/transactions/{transaction-id}.

  6. Guardar el id de la transacción para futuras sincronizaciones.

5.2 Obtener el balance actual en Plain

GET /saas/api/accounts/assignments?start=2020-01-01&end=2026-05-29&transaction_rule_system_id=5&page_size=50&page=0

Parámetros relevantes:

Parámetro

Descripción

start

Fecha de inicio del período del balance. Debe ser una fecha anterior al inicio del uso del sistema externo. Si no hay un valor concreto, basta con una fecha arbitraria antigua, por ejemplo 2020-01-01

end

Fecha final del balance. Normalmente el día actual

transaction_rule_system_id

5 — identifica la regla de transacción del sistema que devuelve el balance (bolsa de horas)

page_size

Tamaño de página recomendado: 50

page

Número de página. Empieza en 0 y se incrementa hasta procesar todos los empleados

Paginación recomendada: pedir páginas de 50 en 50 y procesar cada lote (calcular la compensación y enviar/actualizar la transacción de cada empleado) antes de solicitar la siguiente página.

Estructura de la respuesta (AccountAssignmentSearchBeanDTO):

{
"data": [
{
"employee": {
"id": 12345,
"name": "María",
"surname": "García López",
"email": "[email protected]",
"code": "EMP-001"
},
"counters": [
{
"label": "Balance",
"value": 12.5
}
]
}
],
"pagination": {
"total_items": 245,
"page": 0,
"page_size": 50
}
}

Notas sobre la respuesta:

  • data es la lista de empleados de la página actual.

  • Cada empleado puede identificarse en el sistema externo por data[i].employee.code o data[i].employee.email.

  • Para enviar la transacción se necesita el id interno de Plain: data[i].employee.id.

  • counters siempre contiene un único elemento cuando se filtra por transaction_rule_system_id=5.

  • El balance actual en Plain está en counters[0].value.

  • Para saber si hay más páginas, comprueba si (pagination.page + 1) * pagination.page_size < pagination.total_items.

5.3 Calcular la diferencia

Para cada empleado de la página:

compensación = balance_externo − balance_plain

Donde:

  • balance_externo es el valor del balance que tiene el empleado en el sistema externo.

  • balance_plain es counters[0].value de la respuesta anterior.

Si la diferencia es 0, no es necesario crear ni actualizar ninguna transacción para ese empleado.

5.4 Crear la transacción de compensación (primera sincronización)

POST /saas/api/transactions/

Body de la petición (TransactionDTO):

{
"day": "2026-05-29",
"value": "12.5",
"account_id": 99474,
"employee_id": 12345,
"observations": "Compensación horas objetivo"
}

Campos relevantes:

Campo

Tipo

Descripción

day

string (fecha)

Día de la transacción. Se recomienda usar el día actual

value

string

Valor de compensación calculado. Puede ser positivo o negativo

account_id

integer

99474 — identifica la cuenta de horas trabajadas

employee_id

integer

id del empleado obtenido del endpoint de assignments

observations

string

Texto identificativo. Se recomienda "Compensación horas objetivo" para distinguir fácilmente estas transacciones del resto

La respuesta es un TransactionDTOResponse que incluye el id de la transacción creada:

{
"id": 7654321,
"day": "2026-05-29",
"value": "12.5",
"employee_id": 12345,
"observations": "Compensación horas objetivo",
"account": { "id": 99474, "name": "...", "type": "..." },
"employee": { "id": 12345, "name": "María", "surname": "García López" },
"created": "2026-05-29T08:00:00",
"updated": "2026-05-29T08:00:00",
"version": 1
}

Guarda este id en el sistema externo, asociado al empleado. Será necesario para actualizar la transacción en las siguientes sincronizaciones sin crear una nueva.

5.5 Actualizar la transacción (sincronizaciones posteriores)

Si en el sistema externo ya está guardado el id de la transacción de compensación previa para ese empleado, se actualiza esa misma transacción en lugar de crear una nueva. Esto evita acumular transacciones de compensación a lo largo del tiempo:

PUT /saas/api/transactions/{transaction-id}

Donde {transaction-id} es el id guardado de la transacción previa.

Body de la petición (mismo TransactionDTO, con el nuevo valor calculado):

{
"day": "2026-05-29",
"value": "10.0",
"account_id": 99474,
"employee_id": 12345,
"observations": "Compensación horas objetivo"
}

El day y el value se actualizan al nuevo cálculo de compensación; el id de la transacción se mantiene.

5.7 Multivenue

Al igual que en el resto de operaciones, en un entorno multivenue debes repetir el proceso completo para cada venue, usando el token JWT correspondiente a cada una. Los id de empleado, de transacción y de cuenta son específicos de cada venue, por lo que deben guardarse asociados al venue correspondiente.


Apéndice: Referencia de esquemas

Resumen de las estructuras de datos utilizadas por la API.

AuthenticationDTO — body de la autenticación

Campo

Tipo

Descripción

grant_type

string

Tipo de operación de autenticación (password)

username

string

Usuario con el que iniciar sesión (email)

password

string

Contraseña

TokenDTO — respuesta de la autenticación

Campo

Tipo

Descripción

access_token

string

Token a usar en las peticiones posteriores

token_type

string

Tipo de token

expires_in

integer

Tiempo de validez del token, en segundos

StatusDTO — resultado correcto de una importación

Campo

Tipo

Descripción

status

integer

Código del resultado

ErrorDTO — resultado con error

Campo

Tipo

Descripción

status

integer

Código de estado del error

errors

array de ErrorItemDTO

Detalle de cada error

Los esquemas EmployeeBulkDTO, WorkflowBulkDTO y ErrorItemDTO están detallados en la sección 4.

¿Ha quedado contestada tu pregunta?