Regitap

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.

Reglas que la API no puede saltarse

Son las del registro de jornada (art. 34.9 ET), iguales que en la app:

"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:

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.