Control Horario·Legal
Per a desenvolupadors · API REST v1

API REST

Connecta els teus fitxatges amb el teu ERP, el teu quadre de comandament o les teves pròpies eines. Una API JSON per consultar empleats, centres, fitxatges, presència, horaris previstos, correccions i projectes, amb el teu propi codi de projecte a cada apunt i les hores imputades enfront de les previstes — i, si ho necessites, per registrar fitxatges des del teu propi portal. Disponible en el pla Pro.

Tota clau neix de només lectura. El registre de fitxatges i la resolució de correccions exigeixen un permís d'escriptura que concedim cas per cas: escriure en el registre és escriure en la cadena de hashes que el fa inalterable, i això no s'obre per defecte.

§ 01 · Autenticació

Una API key per empresa.

Cada empresa genera la seva clau des del tauler, a Integracions (/integraciones), amb sessió de RRHH o administració. El token es mostra una sola vegada: a la nostra base de dades només en queda el hash SHA-256 i un prefix llegible perquè puguis identificar la clau al llistat. Pots crear i revocar claus quan vulguis; en revocar-les deixen de funcionar a l'instant. Fins a 10 claus actives per empresa. Tota petició es filtra automàticament per la teva empresa — només veus les teves pròpies dades.

Envia la clau a la capçalera X-Api-Key a cada petició:

curl https://controlhorariolegal.com/api/v1/ping \
  -H "X-Api-Key: chl_tu_clave_aqui"

També s'accepta Authorization: Bearer chl_tu_clave_aqui. Les peticions van sempre per HTTPS i les respostes són JSON amb codificació UTF-8.


§ 02 · Endpoints de consulta

Base: https://controlhorariolegal.com/api/v1

Tots els endpoints d'aquesta secció funcionen amb una clau de només lectura.

GET /ping

Comprova que la teva credencial és vàlida i retorna les dades bàsiques de la teva empresa i pla.

{
  "ok": true,
  "empresa": "Mi Empresa S.L.",
  "cif": "B12345678",
  "plan": "pro",
  "fecha": "2026-06-18T13:30:00+02:00"
}

GET /empleados

Llista els empleats actius de la teva empresa, ordenats per cognoms.

{
  "ok": true,
  "total": 2,
  "empleados": [
    {
      "id": 14,
      "nombre": "Ana",
      "apellidos": "García López",
      "dni": "12345678Z",
      "email": "ana@empresa.es",
      "rol": "empleado",
      "id_centro": 3,
      "requiere_fichaje": true,
      "horas_semana": 40
    }
  ]
}

GET /centros

Llista els centres de treball de la teva empresa.

{
  "ok": true,
  "total": 1,
  "centros": [
    {
      "id": 3,
      "nombre": "Oficina Central",
      "direccion": "Calle Mayor 1",
      "poblacion": "Madrid",
      "codigo_postal": "28013",
      "fichaje_activo": true
    }
  ]
}

GET /fichajes

Llista fitxatges en un rang de dates. Cada fila és un apunt individual (entrada, sortida, pausa_inicio, pausa_fin), no un parell tancat: venen ordenats cronològicament perquè puguis aparellar-los. Paràmetres opcionals:

  • des de, fins a — format YYYY-MM-DD. Per defecte, els últims 30 dies. Màxim 366 dies per consulta.
  • dni — filtra per un empleat (accepta DNI/NIE o correu electrònic).
  • id_empleado — alternativa al DNI, amb l'id retornat per /empleados.
  • id_centro — filtra per un centre.
  • referència — filtra pel teu codi propi de projecte o obra. Coincidència exacta.
  • geo — 1 per incloure la geolocalització de cada fitxatge. Desactivat per defecte.
curl "https://controlhorariolegal.com/api/v1/fichajes?desde=2026-06-01&hasta=2026-06-18" \
  -H "X-Api-Key: chl_tu_clave_aqui"
{
  "ok": true,
  "desde": "2026-06-01",
  "hasta": "2026-06-18",
  "referencia": null,
  "total": 1,
  "truncado": false,
  "fichajes": [
    {
      "id": 9021,
      "empleado": "Ana García López",
      "dni": "12345678Z",
      "centro": "Oficina Central",
      "tipo": "entrada",
      "ts": "2026-06-18 07:02:11.482",
      "fecha": "2026-06-18",
      "hora": "09:02",
      "metodo": "kiosko_pin",
      "referencia": "PRY-2026/014",
      "hash": "a3f1c9...",
      "proyecto": { "id": 7, "codigo": "PRY-2026/014", "nombre": "Reforma Nave B" }
    }
  ]
}

proyecto val null si aquest apunt no està imputat a cap projecte del mestre. Vegeu § 04 · Projectes.

La teva pròpia referència de projecte

Cada fitxatge pot portar una referència lliure de fins a 64 caràcters: el teu codi de projecte, d'obra o d'expedient. És una dada opaca — no la interpretem, només la guardem i te la retornem — i s'escriu en registrar el fitxatge (POST /fichajes). S'admeten lletres, números, espais i els signes . _ - / # : ( ).

La referència no entra en el hash del registre: el hash cobreix el fet jurídic —qui, quan i quin tipus d'apunt—, i aquesta fórmula no canvia. La seva immutabilitat la garanteix el propi motor de base de dades, que no admet modificar ni esborrar un fitxatge un cop escrit.

Si aquesta referència coincideix amb el codigo d'un projecte del teu mestre, el fitxatge queda imputat automàticament a aquest projecte i comença a sumar a /proyectos/horas. Si no coincideix amb cap, la referència es guarda igualment: no rebutgem un fitxatge perquè el projecte no estigui donat d'alta encara.

Geolocalització

Amb ?geo=1 cada fitxatge inclou un objecte geo amb les coordenades registrades, la precisió del GPS en metres, la distància al centre i si ha quedat dins del perímetre configurat. Els camps valen null si aquest fitxatge es va registrar sense ubicació.

"geo": {
  "lat": 40.4321,
  "lng": -3.8123,
  "precision": 18,
  "distancia": 24,
  "valida": true
}

Està fora per defecte expressament: la ubicació d'una persona no hauria de viatjar a un sistema extern sense que algú ho demani explícitament. Si l'exportes a una altra eina, recorda que continua sent una dada personal — reflecteix-ho al teu registre d'activitats de tractament i a la informació que dones a la teva plantilla.

Una consulta retorna com a màxim 10.000 fitxatges. Si s'arriba a aquest límit, truncado val true; acota el rang de dates per obtenir la resta.

GET / hores

Hores treballades per empleat en un període, ja sumades. És la drecera quan el que necessites és el total i no t'interessa aparellar apunts tu mateix. Fa servir exactament el mateix càlcul que la columna d'hores de l'exportació per a nòmina, de manera que la integració i el CSV que obre la gestoria no poden donar xifres diferents. Només compta jornades tancades: una entrada sense sortida és una dada incompleta.

  • des de, fins a — format YYYY-MM-DD. Per defecte, el mes en curs. Màxim 366 dies per consulta.
  • dni — filtra per un empleat (accepta DNI/NIE o correu electrònic).
  • id_empleado — alternativa al DNI.
  • id_centro — filtra per un centre.
curl "https://controlhorariolegal.com/api/v1/horas?desde=2026-08-01&hasta=2026-08-31" \
  -H "X-Api-Key: chl_tu_clave"

{
  "ok": true,
  "desde": "2026-08-01",
  "hasta": "2026-08-31",
  "total": 1,
  "empleados": [
    {
      "id_empleado": 42,
      "nombre": "Ana",
      "apellidos": "García López",
      "dni": "12345678Z",
      "sociedad": "Ejemplo S.L.",
      "horas_semana": 35,
      "dias": 18,
      "segundos": 453600,
      "horas": 126.0,
      "hhmm": "126:00"
    }
  ]
}

GET /estado

Situació actual d' un empleat: si és dins, fora o en pausa, quines accions pot fer ara, el temps treballat avui i si hi ha una absència aprovada que cobreixi el dia. Pensat per pintar el botó de fitxar al teu propi portal sense duplicar les nostres regles de seqüència. Identifica l'empleat amb dni, email o id_empleado.

{
  "ok": true,
  "empleado": { "id": 14, "nombre": "Ana García López", "rol": "empleado",
                "id_centro": 3, "centro": "Oficina Central", "requiere_fichaje": true },
  "fecha": "2026-06-18",
  "ahora": "13:30",
  "solo_kiosko": false,
  "estado": "dentro",
  "acciones": ["pausa_inicio", "salida"],
  "avisar": false,
  "motivo": "jornada_abierta",
  "jornada": { "hora_entrada": "09:02", "horas_abierta": 4.5, "olvidada": false },
  "ausencia": null,
  "hoy": {
    "trabajado": "04:28",
    "en_pausa": false,
    "fichajes": [ { "tipo": "entrada", "hora": "09:02", "metodo": "kiosko_pin" } ]
  }
}

avisar indica si cal recordar-li que fitxi, i motiu explica per què sí o per què no (sin_entrada_hoy, absència, jornada_abierta, solo_kiosko…).

GET /presencia

Qui és dins ara i des de quina hora, per a tota la plantilla obligada a fitxar, en una sola crida. Paràmetre opcional id_centro. És l'equivalent al tauler d'un responsable: amb /estado caldrien tantes crides com empleats.

{
  "ok": true,
  "total": 12,
  "dentro": 7,
  "empleados": [
    {
      "id": 14,
      "nombre": "Ana García López",
      "rol": "empleado",
      "id_centro": 3,
      "centro": "Oficina Central",
      "estado": "dentro",
      "hora_entrada": "09:02",
      "trabajado": "04:28",
      "fichajes_hoy": [ { "tipo": "entrada", "hora": "09:02" } ]
    }
  ]
}

GET /horario

Torns assignats a un empleat en un rang: no quan va fitxar, sinó quan li toca. Identifica l'empleat amb dni, email o id_empleado. des de y fins a són opcionals (per defecte, la setmana en curs); màxim 62 dies per consulta. Els dies es retornen indexats per data, i un mateix dia pot tenir diversos trams (jornada partida).

{
  "ok": true,
  "empleado": { "id": 14, "nombre": "Ana García López" },
  "desde": "2026-06-15",
  "hasta": "2026-06-21",
  "dias": {
    "2026-06-15": [
      { "fecha": "2026-06-15", "id_turno": 2, "nombre": "Mañana",
        "hora_inicio": "09:00", "hora_fin": "17:00", "descanso_min": 30,
        "nota": null, "color": "#2f6f4f", "a_medida": 0 }
    ]
  }
}
§ 03 · Correccions

Oblits: es demanen, s'aproven, no s'editen.

Un fitxatge mai no es modifica ni s'esborra. Quan algú oblida fitxar, es registra una sol·licitud de correcció que algú amb permís aprova o rebutja; si s'aprova, neix un fitxatge nou amb la seva hora, el seu motiu i el rastre de qui l'ha autoritzat. L'API exposa aquest circuit complet.

GET /correcciones/listar

Sol·licituds d'un empleat (dni, email o id_empleado) amb el seu estat i la resposta de RRHH. Últimes 100.

{
  "ok": true,
  "total": 1,
  "correcciones": [
    {
      "id": 341,
      "tipo": "salida",
      "fecha": "2026-06-17",
      "hora": "18:00",
      "motivo": "Olvidé fichar la salida",
      "estado": "aprobado",
      "respuesta": "Confirmado con el encargado"
    }
  ]
}

GET /correcciones/pendientes

Cua de sol·licituds pendents de revisió a tota l'empresa. Paràmetre opcional id_centro. Últimes 200.

{
  "ok": true,
  "total": 1,
  "correcciones": [
    {
      "id": 341,
      "empleado": "Ana García López",
      "id_centro": 3,
      "centro": "Oficina Central",
      "tipo": "salida",
      "fecha": "2026-06-17",
      "hora": "18:00",
      "motivo": "Olvidé fichar la salida",
      "solicitada": "2026-06-18 08:14:02"
    }
  ]
}

POST /correcciones · requereix escriptura

Crea una sol·licitud de correcció. No escriu al registre: el que arriba és una petició, no un apunt. Les hores s'envien en hora local del centre i es converteixen a UTC en guardar-les. El motiu és obligatori (mínim 8 caràcters) i no s'admeten hores futures.

Els apunts han d'encaixar en la seqüència real de l'empleat: no cap una sortida on no hi havia cap jornada oberta, ni una entrada sobre una jornada que ja ho estava. Si alguna cosa no quadra es respon 422 amb el detall i no es crea cap sol·licitud de l'enviament, ni tan sols les que sí encaixaven. Un lot amb entrada i sortida del mateix dia es valida conjuntament, de manera que la sortida es recolza en l'entrada que l'acompanya.

curl -X POST https://controlhorariolegal.com/api/v1/correcciones \
  -H "X-Api-Key: chl_tu_clave_aqui" \
  -H "Content-Type: application/json" \
  -d '{
    "dni": "12345678Z",
    "motivo": "Olvidé fichar la salida del viernes",
    "apuntes": [
      { "tipo": "salida", "ts_solicitado": "2026-06-17 18:00" }
    ]
  }'
{ "ok": true, "n": 1, "ids": [341] }
{
  "ok": false,
  "error": "Ese apunte no encaja: el 17/06 a las 18:00 no había ninguna jornada abierta, así que ahí no puede ir una salida. Lo que encajaría es una entrada."
}

Per a un sol apunt també s'admet el format pla { "tipo": "...", "ts_solicitado": "..." } sense l'array.

POST /correcciones/resolver · requereix escriptura

Aprova o rebutja una sol·licitud. Aprovar insereix el fitxatge que falta a la cadena de hashes, d'aquí el permís d'escriptura. Si qui aprova des del teu portal no existeix com a usuari a Control Horario Legal, envia aprobador_nombre: queda escrit a la resolució per no perdre la traçabilitat.

curl -X POST https://controlhorariolegal.com/api/v1/correcciones/resolver \
  -H "X-Api-Key: chl_tu_clave_aqui" \
  -H "Content-Type: application/json" \
  -d '{
    "id_correccion": 341,
    "resolucion": "aprobado",
    "motivo": "Confirmado con el encargado",
    "aprobador_email": "rrhh@empresa.es",
    "aprobador_nombre": "Luis Martín"
  }'
{ "ok": true, "id": 341, "resolucion": "aprobado" }

§ 04 · Projectes

Hores imputades enfront d'hores previstes.

La referència d'un fitxatge és text lliure i t'és útil si només vols etiquetar. Si a més necessites comparar el que s'ha fitxat amb el que s'ha pressupostat, dona d'alta els teus projectes amb les seves hores previstes i aquesta comparació te la retornem feta: no cal que la reconstrueixis al teu full de càlcul.

Les hores d'una jornada s'atribueixen al projecte de l' apunt d'entrada. Les pauses es descompten. Una jornada oberta sense sortida no es compta: és una dada incompleta, i preferim no sumar-la a inventar un tancament.

GET /proyectos

El teu mestre de projectes. Paràmetre opcional activos=1 per excloure els arxivats.

{
  "ok": true,
  "total": 1,
  "proyectos": [
    {
      "id": 7,
      "codigo": "PRY-2026/014",
      "nombre": "Reforma Nave B",
      "horas_previstas": 120.0,
      "fecha_inicio": "2026-07-01",
      "fecha_fin": "2026-09-30",
      "activo": true
    }
  ]
}

horas_previstas val null si el projecte no té pressupost d'hores; aleshores no es calcula la desviació.

GET /proyectos/horas

Hores imputades per projecte en un període, amb la desviació enfront de les previstes i el percentatge consumit. És l'endpoint que resol el cas d'ús complet en una sola crida.

  • des de, fins a — format YYYY-MM-DD. Per defecte, els últims 30 dies. Màxim 366 dies per consulta.
  • dni, email o id_empleado — limita el càlcul a un sol treballador.
curl "https://controlhorariolegal.com/api/v1/proyectos/horas?desde=2026-08-01&hasta=2026-08-31" \
  -H "X-Api-Key: chl_tu_clave"

{
  "ok": true,
  "desde": "2026-08-01",
  "hasta": "2026-08-31",
  "proyectos": [
    {
      "id_proyecto": 7,
      "codigo": "PRY-2026/014",
      "nombre": "Reforma Nave B",
      "segundos": 411840,
      "horas": 114.4,
      "hhmm": "114:24",
      "horas_previstas": 120.0,
      "desviacion": -5.6,
      "porcentaje": 95.3
    }
  ],
  "sin_imputar": { "segundos": 28800, "horas": 8.0, "hhmm": "08:00" }
}
  • desviacion — hores reals menys previstes. En negatiu, queda marge; en positiu, s'ha passat.
  • porcentaje — consum sobre el previst. null si el projecte no té hores previstes.
  • sin_imputar — hores treballades en el període que no estan assignades a cap projecte. Si aquesta xifra creix, és que falta imputar.

Els projectes actius sense hores en el rang apareixen a zero, perquè «zero hores imputades» és exactament el que necessites veure en un informe de seguiment.

POST /proyectos/imputar · requereix escriptura

Assigna un fitxatge ja registrat a un projecte, o el desassigna. No modifica el fitxatge — això és impossible per disseny — sinó la dada de gestió que hi penja. Tot i així exigeix escriptura, perquè és el número sobre el qual algú acaba facturant.

  • id_fichaje — obligatori.
  • codigo o id_proyecto — el projecte destí. Enviar tots dos buits o absents desimputa el fitxatge.
  • motiu — opcional, queda al registre de la imputació.
curl -X POST https://controlhorariolegal.com/api/v1/proyectos/imputar \
  -H "X-Api-Key: chl_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "id_fichaje": 9021,
    "codigo": "PRY-2026/014",
    "motivo": "Reasignado desde AppSheet"
  }'
{ "ok": true, "id_fichaje": 9021, "id_proyecto": 7, "id_imputacion": 512 }

Si el codi no existeix al teu mestre, la resposta és 404. La imputació guarda historial: l'anterior no s'esborra, es substitueix per la nova com a vigent.


§ 05 · Registre de fitxatges

Fitxar des del teu propi portal.

Si els teus treballadors ja entren diàriament a una extranet o intranet vostra, pots deixar que fitxin des d'allà. Requereix una clau amb permís d'escriptura, que concedim cas per cas després de revisar la integració. Escriu-nos i ho veiem.

POST /fichajes · requereix escriptura

  • tipo — obligatori: entrada, sortida, pausa_inicio o pausa_fin.
  • dni, email o id_empleado — obligatori un d'ells.
  • id_centro — opcional; per defecte, el centre de l'empleat.
  • origen — extranet si el llança el propi treballador des del vostre portal (queda distingible al tauler), api en un altre cas.
  • lat, lng, precision — opcionals. Si els envies, es guarden com a evidència.
  • ip, user_agent — opcionals: la IP de la petició és la del teu servidor, no la del treballador. Si ens passes les reals, es registren aquestes.
  • referència — el teu codi de projecte o obra, màxim 64 caràcters. Una referència invàlida retorna 422: no la retallem en silenci.
  • observaciones — text lliure, màxim 500 caràcters.
curl -X POST https://controlhorariolegal.com/api/v1/fichajes \
  -H "X-Api-Key: chl_tu_clave_aqui" \
  -H "Content-Type: application/json" \
  -d '{ "dni": "12345678Z", "tipo": "entrada", "origen": "extranet" }'
{
  "ok": true,
  "id": 9021,
  "empleado": 14,
  "tipo": "entrada",
  "ts": "2026-06-18 07:02:11.482",
  "hora_local": "09:02:11",
  "referencia": "PRY-2026/014",
  "duplicado": false,
  "hash": "a3f1c9..."
}

L'API respecta la política del centre: si un empleat està configurat per fitxar només al quiosc, la petició es rebutja amb 409. Un fitxatge repetit en pocs segons es considera duplicat i no trenca la cadena: es retorna duplicado: true.

§ 06 · Notes tècniques

Hores, límits i actualització.

Zona horària

El camp ts va sempre en UTC, amb precisió de mil·lisegons: és el valor sobre el qual es calcula el hash del registre. Els camps fecha y hora vénen ja convertits a la zona horària del centre, perquè no hagis de replicar aquesta conversió — és just on s'escola els errors d'una hora. Per bolcar a un full de càlcul o a un informe, fes servir fecha y hora.

Límits

  • 180 unitats per minut i 6.000 per hora per cada clau. No totes les crides costen el mateix: /ping, /empleados, /centros y /proyectos costen 1; /estado, /horario, les de correccions i /proyectos/imputar costen 2; / hores y /presencia, 4; /fichajes y /proyectos/horas, 8. Són 22 consultes seguides de les més cares per minut: molt per sobre de qualsevol integració normal, i és aquí perquè un bucle mal tancat no et deixi sense servei.
  • A més hi ha un límit per IP i un tall per credencials invàlides repetides, pensats contra abús extern. Una integració amb la clau ben posada no els assoleix.
  • /fichajes: màxim 366 dies per consulta i 10.000 registres per resposta.
  • /horario: màxim 62 dies per consulta.
  • /proyectos/horas: màxim 366 dies per consulta.
  • /correcciones/listar: últimes 100. /correcciones/pendientes: últimes 200.
  • Fins a 10 claus actives per empresa.

Cada resposta inclou X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset, perquè la teva integració sàpiga quant li queda sense haver-ho d'endevinar. Si esgotes el marge, la resposta és 429 amb la capçalera Retry-After i un camp retry_after al cos, tots dos en segons: espera'ls i torna-ho a intentar.

Actualització de dades

No disposem de webhooks sortints. Per mantenir sincronitzat un sistema extern, consulta /fichajes periòdicament amb un rang acotat — cada 15 minuts si necessites gairebé temps real, o una passada nocturna del dia anterior per a informes. Per a un tauler de presència en viu, /presencia resol tota la plantilla en una sola crida.

§ 07 · Errors

Respostes d'error.

Quan alguna cosa falla, la resposta és { "ok": false, "error": "..." } amb el codi HTTP corresponent:

Codi Significat
201Creat (fitxatge registrat o correcció sol·licitada).
401Falta l'API key o és invàlida/revocada.
403El teu pla no inclou l'API (disponible en Pro), o la clau és de només lectura i l'endpoint exigeix escriptura.
404Recurs no trobat (p. ex. empleat inexistent).
405Mètode HTTP no permès en aquest endpoint.
409Conflicte amb la configuració del centre (p. ex. empleat en mode només quiosc).
429Límit d'ús superat. Torna-ho a intentar passats els segons que indica Retry-After.
422Dades invàlides (dates mal formades, rang excessiu, tipus erroni…).

Llest per integrar?

Genera la teva API key des del panell, a Integracions. L'API s'inclou en el pla Pro i també durant els 30 dies de prova.

Veure plans →