API de Regitap (v1)
Para que otro software (un CRM, un planificador de servicios, un ERP…), o el agente de IA que lo integra, mantenga el equipo, los lugares y las tareas de cada día en Regitap, y fiche en nombre de los trabajadores desde su propia app. Es genérica: no hay nada específico de ningún software. Rentalotop es un cliente más.
- Base:
https://www.regitap.com/api/v1(dev:https://regitap-dev.up.railway.app/api/v1). - Autenticación:
Authorization: Bearer rgt_…. La clave la crea un administrador en Ajustes → API; se ve una sola vez y se guarda en una variable de entorno del software que la usa (REGITAP_API_KEY): nunca en el código, en un repositorio ni en el chat con un agente. Actúa como un administrador de su empresa y nada más. Todo lo que hace queda en la auditoría con el id de la clave. Revocarla es inmediato. - Formato: JSON (UTF-8). Fechas
YYYY-MM-DD; horasHH:MM(hora local de la empresa); instantes ISO 8601 en UTC (2026-10-23T08:02:00.000Z). - Ids: cada recurso se identifica con tu id (
ref, hasta 100 caracteresA-Z a-z 0-9 . _ : -).PUTcrea o actualiza (idempotente): puedes reenviar lo mismo sin miedo a duplicados. - Errores:
{ "error": "code", "details": [{ "path", "message" }]? }con el código HTTP:400 invalid,401 unauthorized,403 forbidden,404 not_found,409(conflicto de estado),422(referencia desconocida:worker,place),413 too_large,429(límites, abajo). - Límites de cada cuenta (para proteger el servicio; se amplían si hace falta, escribiendo a Regitap):
600 peticiones por minuto por empresa, peticiones de hasta 64 KB, una clave activa por empresa (para
cambiarla, se revoca y se crea otra), 100 tareas por trabajador y día, 1.000 lugares, 100 equipos y un máximo de trabajadores activos por cuenta
(200 de entrada) por encima del tope que ponga la empresa. Al llegar a uno:
429(o409 worker_cap_reached) con unmessage: no reintentes, avisa a una persona. - Tope de trabajadores: la empresa puede fijar un máximo de trabajadores activos (Ajustes → API). Al
pasarlo,
PUT /workers/{ref}responde409 worker_cap_reached(concap): no reintentes, avisa a una persona para que lo suba. Es solo un límite: se paga por los trabajadores que hay, no por el tope.
Reglas que la API no puede saltarse
Son las del registro de jornada (art. 34.9 ET), iguales que en la app:
- El registro no se borra ni se edita: un error se corrige desde Regitap, con motivo y rastro.
- La hora de un fichaje es la del servidor al recibirlo. Solo si el móvil estaba sin conexión
(
offline: true) vale la hora del móvil (deviceAt), marcada como tal y nunca en el futuro. - La ubicación solo del momento de pulsar (
location), nunca seguimiento. Fuera de un lugar se guarda marcado, no se bloquea. - Reintentos seguros: cada pulsación lleva un
clientIdúnico (8–64 caracteres); repetirla devuelve el primer resultado.
"location": { "status": "ok", "lat": 40.4168, "lng": -3.7038, "accuracyM": 12 }
"location": { "status": "denied" } // o "unavailable", "not_requested"
Trabajadores
GET /workers
Todos (también los de baja, con endDate).
PUT /workers/{ref}
Crea o actualiza con lo que tengas: solo cambian los campos que mandes (lo que se haya rellenado
en Regitap no se pierde). Para crear basta el nombre; lo demás (documento, alta, contrato, horas)
se puede completar en Regitap, y la ficha sale como «incompleta» hasta entonces (puede fichar igual).
Si la persona ya está en Regitap sin ref, se enlaza por email o móvil (el de su ficha o el de la cuenta con la que entra en Regitap) en vez de duplicarla
(linked: true; 409 conflict si coinciden varias). Todos los campos:
{
"firstName": "Ana", "lastName": "López García",
"idType": "DNI", "idNumber": "12345678Z",
"phone": "+34600000001", "email": "ana@example.com",
"startDate": "2026-01-12", "endDate": null,
"contractType": "Indefinido", "weeklyHours": 30,
"custom": { "Nº Seguridad Social": "281234567890" },
"schedule": {
"validFrom": "2026-10-26",
"week": [
{ "ranges": ["08:00-14:30"], "breakMinutes": 30, "breakPaid": false },
{ "ranges": ["08:00-14:30"], "breakMinutes": 30, "breakPaid": false },
{ "ranges": ["08:00-14:30"], "breakMinutes": 30, "breakPaid": false },
{ "ranges": ["08:00-14:30"], "breakMinutes": 30, "breakPaid": false },
{ "ranges": ["08:00-14:30"], "breakMinutes": 30, "breakPaid": false },
{ "ranges": [], "breakMinutes": 0, "breakPaid": false },
{ "ranges": [], "breakMinutes": 0, "breakPaid": false }
]
}
}
idType: DNI, NIE o PASSPORT. phone en E.164 (o español, se normaliza). custom: los campos
de la ficha que la empresa haya creado, por su nombre. schedule (opcional): horario semanal
(lunes → domingo) desde una fecha; los días anteriores conservan el que tenían.
Respuesta: { ref, id, created, linked, missing } (missing: lo que le falta a la ficha). 201 creado,
200 actualizado o enlazado, 409 si el documento ya es de otro trabajador.
POST /workers/{ref}/end
Baja: { "endDate": "2026-12-31", "reason": "Fin de contrato" }. No se borra nada.
GET /workers/{ref}/status
Estado ahora (OFF, WORKING, PAUSED), desde cuándo, segundos trabajados hoy, jornada sin
cerrar, ausencia de hoy y tareas de hoy.
POST /workers/{ref}/clock
Fichar en nombre del trabajador (su pulsación en tu app):
{ "action": "DAY_START", "clientId": "a1b2c3d4e5", "location": { "status": "ok", "lat": 40.41, "lng": -3.70, "accuracyM": 15 } }
action: DAY_START, PAUSE_START, PAUSE_END, DAY_END. Sin conexión: "offline": true, "deviceAt": "<instante del móvil>". 409 con reason (invalid_state, not_employed,
company_inactive, paused, ended_today) y el estado real. Terminar la jornada termina la tarea en curso.
Antes de su primer fichaje, la persona tiene que haber leído y aceptado en Regitap la información sobre su registro
(si no, 409 not_informed; ábrele su registro con POST /workers/{ref}/record-link para que la lea).
Una jornada por día: tras DAY_END, otro DAY_START ese mismo día se rechaza con ended_today (la jornada
partida va con pausa; un fin antes de tiempo se corrige). El turno de noche cuenta para el día en que empezó.
Equipos
Cada trabajador está en un equipo como mucho, y los supervisores del equipo (trabajadores también) le aprueban las correcciones y las vacaciones. Lo de un supervisor lo aprueba un administrador, que además puede aprobarlo todo. Crea primero los trabajadores y después los equipos.
PUT /teams/{ref}
{ "name": "Turno de mañana", "supervisors": ["emp-7"], "members": ["emp-1", "emp-2", "emp-7"] } (refs de
trabajadores). Poner a alguien en members lo saca de su equipo anterior. Sin members o sin
supervisors, se quedan como estaban. 422 worker con missing si algún ref no existe.
GET /teams · DELETE /teams/{ref}
Los equipos con sus supervisores y miembros; DELETE lo archiva (sus personas se quedan sin equipo).
También se puede poner el equipo de una persona al darla de alta: PUT /workers/{ref} con "team": "<ref del equipo>"
(null = sin equipo).
Lugares
PUT /places/{ref}
Un cliente, una oficina, una obra…: { "name", "address", "lat", "lng", "radiusM"?, "isCenter"? }.
Un fichaje o una tarea dentro del radio cuenta como «en el lugar». isCenter: centro de trabajo
(sale en los informes).
DELETE /places/{ref}
Lo archiva (los fichajes antiguos siguen apuntando a él).
Tareas
Una tarea es un trabajo de un día dentro de la jornada (una visita a un cliente, un servicio, una cita…). No es el registro de jornada (eso son los fichajes), pero lo mueve:
- Empezar la primera tarea empieza la jornada si no estaba empezada (o termina la pausa), con esa hora y esa ubicación.
- Una a la vez: empezar otra termina la que estaba en curso.
- Entre tareas la jornada sigue: los desplazamientos entre clientes son tiempo de trabajo (TJUE, C-266/14). Nada la pausa ni la termina sola.
- Terminar la última del día devuelve
askEndDay: true: pregunta a la persona si ha terminado la jornada y, si dice que sí,POST /workers/{ref}/clockconDAY_END. Nunca se termina por ella.
PUT /tasks/{ref}
{
"worker": "emp-1", "date": "2026-10-24", "title": "Revisión anual · Cliente Martínez",
"address": "Calle Mayor, 10, Madrid", "place": "cliente-17",
"plannedStart": "10:00", "plannedMinutes": 90, "notes": "Preguntar en recepción"
}
worker: el ref del trabajador. place (opcional): el ref de un lugar, para saber si empezó y
terminó allí. Una tarea ya empezada no cambia de trabajador ni de día (409 started).
"informative": true: solo para verla. Le sale en su app con su hora y dirección, sin Empezar ni Terminar y sin
duración; ficha solo su jornada. Empezarla o terminarla da 409 informative.
GET /tasks?date=2026-10-24&worker=emp-1
Las del día (worker opcional), con status (PENDING, STARTED, DONE, CANCELLED) y las horas
reales (startedAt, endedAt). GET /tasks/{ref}: una.
DELETE /tasks/{ref}
La cancela (solo si no ha empezado; 409 started si sí).
POST /tasks/{ref}/start · POST /tasks/{ref}/end
La pulsación del trabajador en tu app: { "clientId", "location", "offline"?, "deviceAt"? }.
start responde { ok, outsidePlace }; end, { ok, askEndDay }.
Correcciones
Un olvido o una hora mal no se arreglan editando: se pide una corrección, alguien la aprueba o la
rechaza, y si se aprueba se añade un fichaje que sustituye o anula el original (que se queda como estaba).
Por la API una petición nunca se aprueba sola: espera a POST /corrections/{id}/decide. Las dos
llevan el nombre de quién pide y de quién decide (la trabajadora y su encargada en tu software).
GET /workers/{ref}/events?from=2026-10-20&to=2026-10-24
Los fichajes que cuentan (con las correcciones ya aplicadas), cada uno con su id: lo que se corrige.
POST /corrections
{ "worker": "emp-1", "kind": "add", "action": "DAY_END", "proposedAt": "2026-10-22T14:05:00.000Z",
"reason": "Se me olvidó fichar la salida", "requestedBy": "Ana López (desde el CRM)" }
kind: add (falta un fichaje: action y proposedAt), change (otra hora: eventId y
proposedAt) o void (anular: eventId). reason de 3 a 500 caracteres. Responde { id, status: "PENDING" }.
Los encargados y administradores de Regitap la ven en Pendientes y también pueden decidirla allí.
GET /corrections?status=pending
Las que esperan (status=all: también las decididas), con quién las pidió y quién las decidió.
POST /corrections/{id}/decide
{ "approve": true, "note": "Confirmado con el cliente", "decidedBy": "Marta Ruiz", "supervisor": "emp-7" }.
Con supervisor (el ref de quien decide), Regitap comprueba que supervisa el equipo de esa persona y que esa
persona no es supervisora (eso va a un administrador): si no, 403 not_supervisor. Sin supervisor, es una
decisión de administrador (la de la clave).
409 decided si ya estaba decidida; 409 target si el fichaje ya se corrigió por otro lado.
Días libres y ausencias
Vacaciones, días libres, bajas y permisos, entre dos fechas (las dos incluidas). Esos días la persona no
tiene horas previstas ni recibe avisos de «¿has empezado?», y las vacaciones y los días libres descuentan
de sus días del año. Si tu software ya los sabe, mándalos; en Regitap se pueden mantener, anular o añadir
más, y lo que cambie allí te llega por el webhook absence.changed.
PUT /absences/{ref}
{ "worker": "emp-1", "kind": "vacation", "from": "2026-08-03", "to": "2026-08-14", "note": "Agosto" }
kind: vacation, personal_day, sick_leave, paid_leave, unpaid_leave u other. Volver a
mandarla la actualiza (y la reactiva si estaba anulada). 422 worker si no existe esa persona.
DELETE /absences/{ref}
La anula (no se borra). Si venía de una petición aprobada, la petición queda cancelada y los días vuelven.
GET /absences?from=2026-08-01&to=2026-08-31&worker=emp-1
Todas las que tocan esas fechas (un año como mucho), vengan de donde vengan: tuyas (con ref), añadidas
en Regitap o peticiones aprobadas (ref: null). Las anuladas salen con cancelled: true. Úsalo antes de
asignar trabajo a alguien.
Ver su registro sin cuenta en Regitap
Solo si tu equipo ficha dentro de tu software. La ley le da a cada persona derecho a ver su registro. Si no tiene cuenta en Regitap, tu software le abre un enlace.
POST /workers/{ref}/record-link
Responde 201 { "ref", "url", "expiresAt" }. La url vale una vez y durante 10 minutos: ábrela al
momento para esa persona (botón «Ver mi registro» o «Pedir vacaciones»). Le da una hora en la que ve su
registro, sus resúmenes mensuales y sus vacaciones, y pide correcciones y vacaciones. No crea cuenta. Lo que
pide queda a su nombre y lo decide la empresa en Regitap. No guardes la url ni la mandes por email.
Webhooks
Regitap avisa a tu software de lo que pasa, para que no tenga que preguntar. Una dirección por empresa:
la pone una persona en Ajustes → API → Avisos (o tu software con PUT /webhook). Tiene que ser
https:// y pública.
PUT /webhook · GET /webhook · DELETE /webhook · POST /webhook/test
PUT { "url": "https://tu-software.com/api/regitap/webhook" } responde el secreto de firma
(whsec_…): guárdalo en REGITAP_WEBHOOK_SECRET. Cambiar la url mantiene el secreto;
"rotateSecret": true da uno nuevo. POST /webhook/test manda un ping y responde lo que contestó tu software.
Qué llega
POST a tu dirección con JSON: { "id": "whd_…", "type": "…", "created_at": "…", "data": { … } }.
data.worker es { id, ref, name } (ref: tu id). Los avisos para la persona traen data.message
(title y body, en español, listos para enseñárselos).
type |
Cuándo | Para enseñar a la persona |
|---|---|---|
worker.reminder |
A la hora de empezar o terminar según su horario y aún no ha fichado (data.kind: missing_start o missing_end) |
Sí |
correction.decided |
Se decide una corrección suya, o alguien corrige su registro (requestedByWorker: false) |
Sí |
leave.decided |
Se aprueba o rechaza una petición de vacaciones o día libre | Sí |
summary.ready |
Su resumen mensual está listo (lo ve con «Ver mi registro») | Sí |
absence.changed |
Se añade, aprueba o anula una ausencia en Regitap (no las que mandas tú) | Para tu planificación |
clock.recorded |
Cada fichaje, venga de donde venga | Para tu software |
task.started · task.ended |
Empieza o termina una tarea (reason: ended, next_task al empezar otra, day_end al terminar la jornada) |
Para tu software |
ping |
POST /webhook/test |
No |
Comprobar la firma
Cabeceras: Regitap-Event, Regitap-Delivery (el id: si llega dos veces, ignora la segunda) y
Regitap-Signature: t=<segundos>,v1=<hex>, donde v1 es el HMAC-SHA256 de "<t>.<cuerpo tal cual>" con
tu secreto. Rechaza si no coincide o si t tiene más de 5 minutos.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRegitap(rawBody, header, secret = process.env.REGITAP_WEBHOOK_SECRET) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Responde 2xx en menos de 10 segundos. Si no, Regitap reintenta (1 min, 5 min, 15 min, 1 h, 3 h, 6 h,
12 h, 24 h) y después lo da por perdido. Los últimos envíos y su resultado se ven en Ajustes → API.