Registro de jornada

Referencia de la API

API REST versión 1 para conectar tu nómina, ERP o BI: empleados, checadas, ausencias y documentos, más webhooks firmados. Disponible en Enterprise y como add-on de Pro.

Descargar especificación OpenAPI 3.1 (JSON)

Autenticación y límites

  • Crea una llave en Panel → API y webhooks y mándala en Authorization: Bearer jak_…. La llave solo se muestra una vez; revocarla la invalida al instante.
  • Cada llave tiene permisos por recurso; una llamada sin el permiso responde 403 API_SCOPE_MISSING.
  • Límite por llave por minuto (cabeceras X-RateLimit-*); al pasarlo responde 429 con Retry-After.
  • Los errores son JSON con code estable y message en español. Si el plan no incluye la API: 402 PLAN_LIMIT.
  • Las checadas por API llevan method=api y la hora oficial la pone el servidor. Nunca se editan ni borran.

Empleados

get/api/v1/employees

Lista de empleados

Hasta 2000 por consulta. Sin salario.

Permiso: employees:read (Consultar empleados)

Parámetros

  • q (query, string)
  • status (query, "active" | "terminated" | "all")
  • workCenterId (query, string (uuid))
  • departmentId (query, string (uuid))
  • positionId (query, string (uuid))

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
employeeNumber *string | null
fullName *string
email *string | null
status *"active" | "terminated"
workCenter *object | null
workCenter.id *string (uuid)
workCenter.name *string
department *object | null
department.id *string (uuid)
department.name *string
position *object | null
position.id *string (uuid)
position.name *string
supervisor *object | null
supervisor.id *string (uuid)
supervisor.name *string
photoUrl *string | nullURL firmada temporal (15 min).
account *"none" | "invited" | "active" | "disabled"
role *"superadmin" | "admin" | "hr" | "supervisor" | "employee" | "auditor" | null
isSample *booleanPersona ficticia de la prueba gratuita.

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

get/api/v1/employees/{id}

Expediente de un empleado

Permiso: employees:read (Consultar empleados)

Parámetros

  • id (ruta, string (uuid), obligatorio)

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
employeeNumber *string | null
fullName *string
email *string | null
status *"active" | "terminated"
workCenter *object | null
workCenter.id *string (uuid)
workCenter.name *string
department *object | null
department.id *string (uuid)
department.name *string
position *object | null
position.id *string (uuid)
position.name *string
supervisor *object | null
supervisor.id *string (uuid)
supervisor.name *string
photoUrl *string | nullURL firmada temporal (15 min).
account *"none" | "invited" | "active" | "disabled"
role *"superadmin" | "admin" | "hr" | "supervisor" | "employee" | "auditor" | null
isSample *booleanPersona ficticia de la prueba gratuita.
firstName *string
lastName *string
secondLastName *string | null
phone *string | null
showPhoneInDirectory *boolean
curp *string | null
rfc *string | null
nss *string | null
birthDate *string | nullYYYY-MM-DD
hireDate *stringYYYY-MM-DD
terminatedAt *string | nullYYYY-MM-DD
contractType *"indefinite" | "fixed_term" | "specific_work" | "seasonal" | "trial_period" | "initial_training"
directReports *integer

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

Checadas

get/api/v1/time-clock/entries

Checadas por rango de días

Rango en la zona horaria de la empresa, máximo 62 días. Las checadas nunca se modifican; las correcciones son eventos aparte.

Permiso: time_entries:read (Consultar checadas)

Parámetros

  • employeeId (query, string (uuid))
  • from (query, string, obligatorio)
  • to (query, string, obligatorio)
  • needsReview (query, "true" | "false")

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
employeeId *string (uuid)
employeeName *string
seq *integerConsecutivo por empleado en la cadena de integridad.
type *"clock_in" | "break_start" | "break_end" | "clock_out"
method *"web" | "pwa" | "kiosk_pin" | "kiosk_qr" | "whatsapp" | "facial" | "api"
serverTimestamp *stringHora oficial (servidor, UTC, ISO 8601).
deviceTimestamp *string | nullHora informativa del dispositivo o sistema.
wasOffline *boolean
syncedAt *string | null
workCenter *object | null
workCenter.id *string (uuid)
workCenter.name *string
timezone *stringZona IANA del centro para mostrar la hora.
lat *number | null
lng *number | null
accuracyM *integer | null
insideGeofence *boolean | null
reviewReasons *"outside_geofence" | "no_location" | "offline" | "unexpected_sequence" | "missing_clock_out" | "other_work_center" | "outside_schedule"[]
entryHash *stringSHA-256(prev_hash || payload), hexadecimal.

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

post/api/v1/time-clock/entries

Registrar una checada (method=api)

La hora oficial la pone el servidor; `deviceTimestamp` es solo informativa. Reintentar con la misma `idempotencyKey` devuelve la misma checada (200). Responde 201 si es nueva.

Permiso: time_entries:write (Registrar checadas (method=api))

Cuerpo (JSON)

CampoTipoDescripción
employeeId *string (uuid)
type *"clock_in" | "break_start" | "break_end" | "clock_out"
idempotencyKey *string (uuid)
deviceTimestampstring (date-time)
workCenterIdstring (uuid)

Respuesta 200 Ya existía (idempotencia).

CampoTipoDescripción
entry *object
entry.id *string (uuid)
entry.employeeId *string (uuid)
entry.employeeName *string
entry.seq *integerConsecutivo por empleado en la cadena de integridad.
entry.type *"clock_in" | "break_start" | "break_end" | "clock_out"
entry.method *"web" | "pwa" | "kiosk_pin" | "kiosk_qr" | "whatsapp" | "facial" | "api"
entry.serverTimestamp *stringHora oficial (servidor, UTC, ISO 8601).
entry.deviceTimestamp *string | nullHora informativa del dispositivo o sistema.
entry.wasOffline *boolean
entry.syncedAt *string | null
entry.workCenter *object | null
entry.workCenter.id *string (uuid)
entry.workCenter.name *string
entry.timezone *stringZona IANA del centro para mostrar la hora.
entry.lat *number | null
entry.lng *number | null
entry.accuracyM *integer | null
entry.insideGeofence *boolean | null
entry.reviewReasons *"outside_geofence" | "no_location" | "offline" | "unexpected_sequence" | "missing_clock_out" | "other_work_center" | "outside_schedule"[]
entry.entryHash *stringSHA-256(prev_hash || payload), hexadecimal.
duplicate *booleantrue: ya existía con esa llave de idempotencia (responde 200, no 201).

Respuesta 201 OK

CampoTipoDescripción
entry *object
entry.id *string (uuid)
entry.employeeId *string (uuid)
entry.employeeName *string
entry.seq *integerConsecutivo por empleado en la cadena de integridad.
entry.type *"clock_in" | "break_start" | "break_end" | "clock_out"
entry.method *"web" | "pwa" | "kiosk_pin" | "kiosk_qr" | "whatsapp" | "facial" | "api"
entry.serverTimestamp *stringHora oficial (servidor, UTC, ISO 8601).
entry.deviceTimestamp *string | nullHora informativa del dispositivo o sistema.
entry.wasOffline *boolean
entry.syncedAt *string | null
entry.workCenter *object | null
entry.workCenter.id *string (uuid)
entry.workCenter.name *string
entry.timezone *stringZona IANA del centro para mostrar la hora.
entry.lat *number | null
entry.lng *number | null
entry.accuracyM *integer | null
entry.insideGeofence *boolean | null
entry.reviewReasons *"outside_geofence" | "no_location" | "offline" | "unexpected_sequence" | "missing_clock_out" | "other_work_center" | "outside_schedule"[]
entry.entryHash *stringSHA-256(prev_hash || payload), hexadecimal.
duplicate *booleantrue: ya existía con esa llave de idempotencia (responde 200, no 201).

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

Ausencias

get/api/v1/leave/requests

Solicitudes de ausencia y vacaciones

Permiso: absences:read (Consultar ausencias y vacaciones)

Parámetros

  • status (query, "pending" | "approved" | "rejected" | "cancelled")
  • employeeId (query, string (uuid))

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
employee *object
employee.id *string (uuid)
employee.name *string
type *object
type.id *string (uuid)
type.name *string
type.category *"vacation" | "sick_general" | "maternity" | "work_risk" | "paternity" | "paid_leave" | "unpaid_leave" | "personal_day"
type.payer *"employer" | "imss" | "unpaid"
type.countMode *"workdays" | "calendar"
startDate *stringYYYY-MM-DD
endDate *stringYYYY-MM-DD
days *integer
reason *string | null
certificateFolio *string | null
status *"pending" | "approved" | "rejected" | "cancelled"
requestedBy *string | null
requestedAt *string
decidedBy *string | null
decidedAt *string | null
decisionNote *string | null
cancelledBy *string | null
cancelledAt *string | null
canDecide *boolean
canCancel *boolean

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

Documentos

get/api/v1/documents/folders

Carpetas (de la empresa o de un empleado)

Las carpetas "Solo RH" y "Confidencial" no son visibles para la API.

Permiso: documents:read (Consultar documentos (sin carpetas confidenciales))

Parámetros

  • employeeId (query, string (uuid))

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
name *string
access *"everyone" | "managers" | "hr_only"
employeeId *string (uuid) | null
system *boolean
documentCount *integer

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

get/api/v1/documents

Documentos de una carpeta

Permiso: documents:read (Consultar documentos (sin carpetas confidenciales))

Parámetros

  • folderId (query, string (uuid), obligatorio)

Respuesta 200 OK

CampoTipoDescripción
id *string (uuid)
folderId *string (uuid)
employeeId *string (uuid) | null
name *string
contentType *string
sizeBytes *integer
sha256 *string
uploadedBy *string | null
createdAt *string
hasSignatures *boolean

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

get/api/v1/documents/{id}/url

URL temporal de descarga

Cada acceso queda en la bitácora de la empresa.

Permiso: documents:read (Consultar documentos (sin carpetas confidenciales))

Parámetros

  • id (ruta, string (uuid), obligatorio)

Respuesta 200 OK

CampoTipoDescripción
url *string
expiresAt *string

Errores: 400 Datos inválidos (VALIDATION_ERROR, con `fields`). · 401 Llave inválida, revocada o vencida (API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED). · 402 El plan de la empresa no incluye la API pública o la función (PLAN_LIMIT). · 403 La llave no tiene el alcance (API_SCOPE_MISSING) o la ruta no es pública. · 404 No existe o es de otra empresa (responden igual). · 429 Límite por minuto de la llave (RATE_LIMITED). Ver `Retry-After`.

Webhooks

Enviamos un POST JSON a tu URL con la cabecera jornada-signature: t=<unix>,v1=<hex>, donde v1 = HMAC-SHA256(secreto, `${t}.${cuerpo}`). Rechaza la entrega si la firma no coincide o si tiene más de 5 minutos. Responde 2xx para confirmarla; cualquier otra respuesta se reintenta con espera creciente. El id del evento es el mismo en cada reintento: úsalo para no procesarlo dos veces.

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: el cuerpo exactamente como llegó (sin re-serializar el JSON).
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}

employee.created Alta de empleado

CampoTipoDescripción
id *string (uuid)Id del evento (igual en todos los reintentos).
type *"employee.created"
createdAt *string
tenantId *string (uuid)
data *object
data.id *string (uuid)
data.employeeNumber *string | null
data.firstName *string
data.lastName *string
data.secondLastName *string | null
data.email *string | null
data.workCenterId *string (uuid) | null
data.departmentId *string (uuid) | null
data.positionId *string (uuid) | null
data.hireDate *string
data.contractType *"indefinite" | "fixed_term" | "specific_work" | "seasonal" | "trial_period" | "initial_training"

time_entry.created Checada registrada

CampoTipoDescripción
id *string (uuid)Id del evento (igual en todos los reintentos).
type *"time_entry.created"
createdAt *string
tenantId *string (uuid)
data *object
data.id *string (uuid)
data.employeeId *string (uuid)
data.seq *integer
data.type *"clock_in" | "break_start" | "break_end" | "clock_out"
data.method *"web" | "pwa" | "kiosk_pin" | "kiosk_qr" | "whatsapp" | "facial" | "api"
data.serverTimestamp *string
data.deviceTimestamp *string | null
data.workCenterId *string (uuid) | null
data.insideGeofence *boolean | null
data.reviewReasons *"outside_geofence" | "no_location" | "offline" | "unexpected_sequence" | "missing_clock_out" | "other_work_center" | "outside_schedule"[]
data.entryHash *string

time_off.approved Ausencia o vacaciones aprobadas

CampoTipoDescripción
id *string (uuid)Id del evento (igual en todos los reintentos).
type *"time_off.approved"
createdAt *string
tenantId *string (uuid)
data *object
data.id *string (uuid)
data.employeeId *string (uuid)
data.leaveTypeId *string (uuid)
data.leaveTypeCode *string
data.startDate *string
data.endDate *string
data.days *integer
data.decidedAt *string | null

payroll_export.generated Exportación de incidencias generada

CampoTipoDescripción
id *string (uuid)Id del evento (igual en todos los reintentos).
type *"payroll_export.generated"
createdAt *string
tenantId *string (uuid)
data *object
data.id *string (uuid)
data.periodKind *string
data.periodFrom *string
data.periodTo *string
data.kind *"original" | "complementary"
data.sequence *integer
data.baseExportId *string (uuid) | null
data.employees *integer
data.changedEmployees *integer
data.contentHash *string