{"openapi":"3.1.0","info":{"title":"DutyBeat API","version":"1.0.0","description":"API pública de DutyBeat. Autentícate con una clave de API (Authorization: Bearer db_live_…). Crea claves en la app: Configuración → Claves de API."},"servers":[{"url":"https://api.dutybeat.com"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"Clave de API (db_live_…)"}}},"security":[{"ApiKeyAuth":[]}],"paths":{"/api/v1/me":{"get":{"operationId":"me","summary":"Identidad de la clave","description":"Devuelve a qué empresa (tenant) pertenece tu clave de API, qué métodos tiene habilitados (scopes) y con qué usuario actúa (acts_as_user): la identidad cuyos permisos autorizan las escrituras de la clave. No requiere ningún permiso concreto: cualquier clave válida puede consultarse a sí misma. Úsalo para verificar que la clave funciona y descubrir qué puede hacer.","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"tenant":{"id":"tenant-001","name":"Atlántica Ingeniería S.L."},"scopes":["users.list","attendance.list"],"acts_as_user":{"id":"u-dirg","email":"alberto.vazquez@atlantica-ing.com","name":"Alberto Vázquez Romero"}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/users/{user_id}":{"get":{"operationId":"users.get","summary":"Leer usuario","description":"Devuelve la ficha completa de un usuario: los mismos datos de cuenta y perfil que ve en \"Mi Perfil\" dentro de la app. Un usuario de otro tenant o inexistente devuelve 404.","parameters":[{"name":"user_id","in":"path","required":true,"description":"Identificador del usuario dentro de tu empresa.","schema":{"type":"string"},"example":"u-delm"},{"name":"include_folders","in":"query","required":false,"description":"Si es true, incluye las carpetas de documentos del empleado (solo metadatos).","schema":{"type":"boolean"},"example":"true"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"u-delm","full_name":"Javier Ortega Marín","email":"javier.ortega@atlantica-ing.com","role":"member","status":"active","hire_date":"2016-09-01","vacation_days":23,"works_holidays":false,"remote_work_allowed":false,"overtime_compensation":null,"overtime_comp_expiry":null,"created_at":"2016-09-01T00:00:00Z","has_photo":false,"department":{"id":"dept-delm","name":"Delegación Madrid"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"},"profile":{"phone_company":"+34 910 100 007","phone_personal":"+34 600 100 007","personal_email":"javier.ortega@gmail.com","birth_date":"1978-06-27","nationality":"Española","dni":"50112239G","dni_expiry":"2031-06-27","address":"Calle de Princesa 30, 5ºB","city":"Madrid","postal_code":"28008","passport_number":"PAH238910","passport_issue":"2022-04-11","passport_expiry":"2032-04-10","health_insurance":"Adeslas — Póliza nº 4471902 (cobertura internacional)","iban":"ES9121000418450200051332","swift":"CAIXESBBXXX","country":"España","company_name":"Atlántica Ingeniería S.L.","company_cif":"B70123456","ssn":"28/12345678/40","qualifications":["Ingeniero de Caminos, Canales y Puertos — Universidad Politécnica de Madrid (2009)"]},"folders":[{"id":"contratos","name":"Contratos","document_count":1},{"id":"cursos","name":"Cursos de Formación","document_count":1},{"id":"gastos","name":"Gastos","document_count":2},{"id":"nominas","name":"Nóminas","document_count":2},{"id":"prl","name":"Seguridad y Salud Laboral","document_count":1}]}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}},"patch":{"operationId":"users.update","summary":"Editar usuario","description":"Modifica los datos de un empleado (cuenta y perfil). Es parcial: solo cambia los campos que incluyas; lo que omites se mantiene, y enviar un campo con null lo borra. No cambia el email ni el estado (para dar de baja usa el método correspondiente). Cambiar el rol a \"admin\" requiere que la clave actúe como administrador. Devuelve la ficha del usuario ya actualizada.","parameters":[{"name":"user_id","in":"path","required":true,"description":"Identificador del usuario dentro de tu empresa.","schema":{"type":"string"},"example":"u-estm1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"full_name":{"type":"string","description":"Nombre completo."},"role":{"type":"string","description":"\"member\" o \"admin\" (admin requiere clave admin)."},"department_id":{"type":"string","description":"Departamento (id); null lo desasigna."},"work_center_id":{"type":"string","description":"Sede / centro de trabajo (id); null la desasigna."},"hire_date":{"type":"string","description":"Fecha de alta (YYYY-MM-DD) o null."},"vacation_days":{"type":"integer","description":"Días de vacaciones anuales o null."},"works_holidays":{"type":"boolean","description":"Si trabaja en festivos."},"remote_work_allowed":{"type":"boolean","description":"Si puede fichar en modalidad remota."},"phone_company":{"type":"string","description":"Teléfono de empresa."},"phone_personal":{"type":"string","description":"Teléfono personal."},"personal_email":{"type":"string","description":"Correo electrónico personal (distinto del de acceso)."},"birth_date":{"type":"string","description":"Fecha de nacimiento (YYYY-MM-DD)."},"nationality":{"type":"string","description":"Nacionalidad."},"dni":{"type":"string","description":"DNI o NIE. De sus dígitos sale el identificador de fichaje del kiosko."},"dni_expiry":{"type":"string","description":"Caducidad del DNI/NIE (YYYY-MM-DD)."},"address":{"type":"string","description":"Dirección postal."},"city":{"type":"string","description":"Ciudad."},"postal_code":{"type":"string","description":"Código postal."},"passport_number":{"type":"string","description":"Número de pasaporte."},"passport_issue":{"type":"string","description":"Fecha de emisión del pasaporte (YYYY-MM-DD)."},"passport_expiry":{"type":"string","description":"Fecha de caducidad del pasaporte (YYYY-MM-DD)."},"health_insurance":{"type":"string","description":"Mutua o seguro médico."},"iban":{"type":"string","description":"IBAN de la cuenta de nómina."},"swift":{"type":"string","description":"Código BIC/SWIFT del banco."},"country":{"type":"string","description":"País. Campo gestionado por RRHH."},"company_name":{"type":"string","description":"Razón social a la que está adscrito. Campo gestionado por RRHH."},"company_cif":{"type":"string","description":"CIF de esa razón social. Campo gestionado por RRHH."},"ssn":{"type":"string","description":"Número de afiliación a la Seguridad Social. Campo gestionado por RRHH."},"qualifications":{"type":"string","description":"Titulaciones, como lista JSON serializada. Campo gestionado por RRHH."}}},"example":{"remote_work_allowed":true,"iban":"ES9121000418450200051332"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"u-estm1","full_name":"Andrés Molina Prieto","email":"andres.molina@atlantica-ing.com","role":"member","status":"active","hire_date":"2016-09-01","vacation_days":23,"works_holidays":false,"remote_work_allowed":true,"overtime_compensation":null,"overtime_comp_expiry":null,"created_at":"2016-09-01T00:00:00Z","has_photo":false,"department":{"id":"dept-estm","name":"Estructuras – Madrid"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"},"profile":{"phone_company":"+34 910 100 010","phone_personal":"+34 600 100 010","personal_email":"andres.molina@gmail.com","birth_date":"1985-03-14","nationality":"Española","dni":"50223344B","dni_expiry":"2030-03-14","address":"Calle Mayor 12","city":"Madrid","postal_code":"28013","passport_number":null,"passport_issue":null,"passport_expiry":null,"health_insurance":null,"iban":"ES9121000418450200051332","swift":null,"country":null,"company_name":null,"company_cif":null,"ssn":null,"qualifications":[]},"folders":null}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/users":{"get":{"operationId":"users.list","summary":"Listar usuarios","description":"Devuelve una lista paginada de usuarios de tu empresa (25 por página, máximo 100). Admite filtros por estado, cargo, departamento, sede y una búsqueda por nombre o email. Por defecto cada usuario trae campos básicos (detail=reduced); con detail=full trae la ficha completa (igual que el endpoint de un usuario). La respuesta incluye un objeto meta con page, page_size y total.","parameters":[{"name":"page","in":"query","required":false,"description":"Página a devolver (empieza en 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Resultados por página (por defecto 25, máximo 100).","schema":{"type":"integer"},"example":"25"},{"name":"detail","in":"query","required":false,"description":"Nivel de detalle: \"reduced\" (por defecto) o \"full\" (ficha completa con perfil).","schema":{"type":"string"},"example":"full"},{"name":"status","in":"query","required":false,"description":"Filtra por estado: \"active\" o \"disabled\".","schema":{"type":"string"},"example":"active"},{"name":"role","in":"query","required":false,"description":"Filtra por cargo: \"member\" o \"admin\".","schema":{"type":"string"},"example":"member"},{"name":"department_id","in":"query","required":false,"description":"Filtra por departamento (id).","schema":{"type":"string"},"example":"dep-ing"},{"name":"work_center_id","in":"query","required":false,"description":"Filtra por sede / centro de trabajo (id).","schema":{"type":"string"},"example":"wc-cor"},{"name":"email","in":"query","required":false,"description":"Resuelve un email exacto (sin distinguir mayúsculas) a su usuario. Devuelve como mucho un resultado; úsalo para mapear tu identificador externo (el email) a nuestro id.","schema":{"type":"string"},"example":"javier.ortega@atlantica-ing.com"},{"name":"q","in":"query","required":false,"description":"Búsqueda por nombre completo o email (subcadena). Para un email exacto usa `email`.","schema":{"type":"string"},"example":"daniel"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"u-dirg","full_name":"Alberto Vázquez Romero","email":"alberto.vazquez@atlantica-ing.com","role":"admin","status":"active","has_photo":false,"department":{"id":"dept-dir","name":"Dirección General"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"}},{"id":"u-estm1","full_name":"Andrés Molina Prieto","email":"andres.molina@atlantica-ing.com","role":"member","status":"active","has_photo":false,"department":{"id":"dept-estm","name":"Estructuras – Madrid"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"}}],"meta":{"page":1,"page_size":2,"total":17}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}},"post":{"operationId":"users.create","summary":"Crear usuario","description":"Da de alta un empleado en tu empresa (datos de cuenta). Solo email y full_name son obligatorios. La contraseña es opcional: si no la envías, el empleado la fija con \"He olvidado mi contraseña\"; si la envías, mínimo 8 caracteres. Crear un usuario con role=\"admin\" requiere que la clave actúe como un administrador. El email debe ser único. Devuelve la ficha del usuario creado (201).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Email de acceso (único)."},"full_name":{"type":"string","description":"Nombre completo."},"password":{"type":"string","description":"Contraseña inicial (mín. 8). Si se omite, el empleado la fija por reset."},"role":{"type":"string","description":"\"member\" (por defecto) o \"admin\" (requiere clave admin)."},"department_id":{"type":"string","description":"Departamento (id) al que pertenece."},"work_center_id":{"type":"string","description":"Sede / centro de trabajo (id)."},"hire_date":{"type":"string","description":"Fecha de alta (YYYY-MM-DD)."},"vacation_days":{"type":"integer","description":"Días de vacaciones anuales. Por defecto el de la empresa."},"works_holidays":{"type":"boolean","description":"Si trabaja en festivos."},"remote_work_allowed":{"type":"boolean","description":"Si puede fichar en modalidad remota."},"dni":{"type":"string","description":"DNI/NIE: de sus dígitos se deriva el identificador de kiosko."},"phone":{"type":"string","description":"Teléfono: de sus últimos 4 dígitos se deriva el PIN inicial de kiosko."}},"required":["email","full_name"]},"example":{"email":"lucia.prieto@atlantica-ing.com","full_name":"Lucía Prieto Vega","department_id":"dept-estm","work_center_id":"wc-madrid"}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"example":{"data":{"id":"u-nuevo","full_name":"Lucía Prieto Vega","email":"lucia.prieto@atlantica-ing.com","role":"member","status":"active","hire_date":null,"vacation_days":23,"works_holidays":false,"remote_work_allowed":false,"overtime_compensation":null,"overtime_comp_expiry":null,"created_at":"2026-07-11T10:00:00.000Z","has_photo":false,"department":{"id":"dept-estm","name":"Estructuras – Madrid"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"},"profile":{"phone_company":null,"phone_personal":null,"personal_email":null,"birth_date":null,"nationality":null,"dni":null,"dni_expiry":null,"address":null,"city":null,"postal_code":null,"passport_number":null,"passport_issue":null,"passport_expiry":null,"health_insurance":null,"iban":null,"swift":null,"country":null,"company_name":null,"company_cif":null,"ssn":null,"qualifications":[]},"folders":null}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/users/{user_id}/deactivate":{"post":{"operationId":"users.deactivate","summary":"Dar de baja usuario","description":"Desactiva un empleado (offboarding): deja de poder acceder y sus sesiones se cierran de inmediato. Es idempotente (dar de baja a quien ya está de baja también responde 200). No se puede desactivar al último administrador activo (409). Devuelve la ficha del usuario, ya con status \"disabled\".","parameters":[{"name":"user_id","in":"path","required":true,"description":"Identificador del usuario dentro de tu empresa.","schema":{"type":"string"},"example":"u-nuevo"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"u-nuevo","full_name":"Lucía Prieto Vega","email":"lucia.prieto@atlantica-ing.com","role":"member","status":"disabled","hire_date":null,"vacation_days":23,"works_holidays":false,"remote_work_allowed":false,"overtime_compensation":null,"overtime_comp_expiry":null,"created_at":"2026-07-11T10:00:00.000Z","has_photo":false,"department":{"id":"dept-estm","name":"Estructuras – Madrid"},"work_center":{"id":"wc-madrid","name":"Madrid – Sede central"},"profile":{"phone_company":null,"phone_personal":null,"personal_email":null,"birth_date":null,"nationality":null,"dni":null,"dni_expiry":null,"address":null,"city":null,"postal_code":null,"passport_number":null,"passport_issue":null,"passport_expiry":null,"health_insurance":null,"iban":null,"swift":null,"country":null,"company_name":null,"company_cif":null,"ssn":null,"qualifications":[]},"folders":null}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/attendance":{"get":{"operationId":"attendance.list","summary":"Listar fichajes","description":"Devuelve una entrada por día natural del rango (ambos extremos incluidos) para un empleado: las marcas del día, los minutos realmente trabajados —los descansos ya van descontados— y el saldo frente a su horario. Se pagina por día, no por marca. El rango no puede superar 366 días. La geolocalización de las marcas nunca se devuelve.","parameters":[{"name":"user_id","in":"query","required":true,"description":"Id del empleado.","schema":{"type":"string"},"example":"u-delm"},{"name":"from","in":"query","required":true,"description":"Primer día del rango (YYYY-MM-DD).","schema":{"type":"string"},"example":"2026-06-03"},{"name":"to","in":"query","required":true,"description":"Último día del rango (YYYY-MM-DD), incluido.","schema":{"type":"string"},"example":"2026-06-03"},{"name":"tz","in":"query","required":false,"description":"Zona horaria IANA con la que se agrupan las marcas por día.","schema":{"type":"string"},"example":"Europe/Madrid"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Días por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"user_id":"u-delm","date":"2026-06-03","is_workday":true,"holiday":null,"modality":"remote","punches":[{"type":"in","at":"2026-06-03T07:00:00.000Z","edited":false},{"type":"break_start","at":"2026-06-03T08:30:00.000Z","edited":false},{"type":"break_end","at":"2026-06-03T08:45:00.000Z","edited":false},{"type":"out","at":"2026-06-03T12:00:00.000Z","edited":false},{"type":"in","at":"2026-06-03T13:00:00.000Z","edited":false},{"type":"out","at":"2026-06-03T16:00:00.000Z","edited":false}],"worked_minutes":465,"expected_minutes":480,"balance_minutes":-15}],"meta":{"page":1,"page_size":25,"total":1}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}},"post":{"operationId":"attendance.create","summary":"Fichar (empujar marca)","description":"Registra una marca de fichaje de un empleado (entrada, salida o pausa), con su momento real (ts). Pensado para volcar fichajes de un reloj o sistema externo. El registro es INMUTABLE: solo se añade, nunca se edita ni borra (valor legal). Las marcas deben llegar en orden cronológico y en una secuencia coherente (una entrada abierta antes de una salida, etc.); si no, 409. ts no puede ser futuro ni tener más de 60 días. La geolocalización no se acepta ni se devuelve. Requiere que la clave pueda administrar empleados.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"Empleado que fichó."},"type":{"type":"string","description":"Tipo de marca: \"in\", \"out\", \"break_start\" o \"break_end\"."},"ts":{"type":"string","description":"Momento real de la marca (ISO 8601). No futuro, máx. 60 días atrás, posterior a la última marca."},"modality":{"type":"string","description":"Solo para \"in\": \"onsite\" (por defecto) o \"remote\" (si el empleado tiene teletrabajo)."}},"required":["user_id","type","ts"]},"example":{"user_id":"u-delm","type":"in","ts":"2026-07-10T09:00:00Z"}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"example":{"data":{"id":"punch-nueva","user_id":"u-delm","type":"in","ts":"2026-07-10T09:00:00.000Z","modality":"onsite"}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/attendance/summary":{"get":{"operationId":"attendance.summary","summary":"Resumen de jornada (empresa)","description":"Una fila por empleado con sus totales consolidados en el rango [from, to] (ambos incluidos): minutos trabajados —descansos ya descontados—, minutos esperados según su horario y el saldo entre ambos, más los días trabajados y esperados. Es la consulta de nómina (\"horas por empleado este mes\"), la contrapartida a `attendance.list` (que es por empleado y por día). Filtrable por estado, departamento y sede como el listado de empleados. Se pagina por empleado; el rango no puede superar 366 días.","parameters":[{"name":"from","in":"query","required":true,"description":"Primer día del rango (YYYY-MM-DD).","schema":{"type":"string"},"example":"2026-06-01"},{"name":"to","in":"query","required":true,"description":"Último día del rango (YYYY-MM-DD), incluido.","schema":{"type":"string"},"example":"2026-06-30"},{"name":"tz","in":"query","required":false,"description":"Zona horaria IANA con la que se agrupan las marcas por día.","schema":{"type":"string"},"example":"Europe/Madrid"},{"name":"status","in":"query","required":false,"description":"Filtra por estado del empleado: 'active' o 'disabled'.","schema":{"type":"string"},"example":"active"},{"name":"department_id","in":"query","required":false,"description":"Filtra por id de departamento.","schema":{"type":"string"},"example":"dep-ing"},{"name":"work_center_id","in":"query","required":false,"description":"Filtra por id de sede.","schema":{"type":"string"},"example":"wc-madrid"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Empleados por página (máx. 100).","schema":{"type":"integer"},"example":"3"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"user_id":"u-dirg","full_name":"Alberto Vázquez Romero","worked_minutes":2445,"expected_minutes":10560,"balance_minutes":-8115,"worked_days":5,"expected_days":22},{"user_id":"u-estm1","full_name":"Andrés Molina Prieto","worked_minutes":0,"expected_minutes":10560,"balance_minutes":-10560,"worked_days":0,"expected_days":22},{"user_id":"u-delc","full_name":"Antía Ferreiro Lago","worked_minutes":2400,"expected_minutes":10080,"balance_minutes":-7680,"worked_days":5,"expected_days":21}],"meta":{"page":1,"page_size":3,"total":17}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/absences":{"get":{"operationId":"absences.list","summary":"Listar ausencias","description":"Devuelve las ausencias que solapan el rango indicado (ambos extremos incluidos), de la más reciente a la más antigua — incluidas las que empiezan antes o terminan después de la ventana. Filtrable por empleado, estado y tipo. Las ausencias por horas traen `start_time`/`end_time`; las de día completo los traen a null. Las notas de solicitud y decisión no se devuelven.","parameters":[{"name":"from","in":"query","required":true,"description":"Primer día del rango (YYYY-MM-DD).","schema":{"type":"string"},"example":"2026-06-01"},{"name":"to","in":"query","required":true,"description":"Último día del rango (YYYY-MM-DD), incluido.","schema":{"type":"string"},"example":"2026-06-30"},{"name":"user_id","in":"query","required":false,"description":"Solo las ausencias de este empleado.","schema":{"type":"string"},"example":"u-delm"},{"name":"status","in":"query","required":false,"description":"Estado: 'pending', 'approved', 'rejected' o 'cancelled'.","schema":{"type":"string"},"example":"approved"},{"name":"type","in":"query","required":false,"description":"Clave del tipo de ausencia (p. ej. 'vacation', 'sick'). El catálogo es por empresa.","schema":{"type":"string"},"example":"vacation"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Ausencias por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"abs-008","user_id":"u-delm","type":{"key":"vacation","label":"Vacaciones"},"status":"approved","start_date":"2026-06-22","end_date":"2026-06-26","start_time":null,"end_time":null,"created_at":"2026-06-08T00:00:00Z","decided_at":"2026-06-09T09:00:00Z"},{"id":"abs-007","user_id":"u-delm","type":{"key":"justified","label":"Ausencia justificada"},"status":"approved","start_date":"2026-06-15","end_date":"2026-06-15","start_time":null,"end_time":null,"created_at":"2026-06-09T00:00:00Z","decided_at":"2026-06-09T11:00:00Z"},{"id":"abs-010","user_id":"u-delm","type":{"key":"personal","label":"Asuntos propios"},"status":"approved","start_date":"2026-06-11","end_date":"2026-06-11","start_time":"10:00","end_time":"12:00","created_at":"2026-06-05T00:00:00Z","decided_at":"2026-06-05T15:00:00Z"}],"meta":{"page":1,"page_size":25,"total":3}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}},"post":{"operationId":"absences.create","summary":"Crear ausencia","description":"Registra una ausencia en nombre de un empleado (user_id). Entra pendiente de aprobación o ya aprobada según el tipo, igual que si la solicitara el propio empleado en la app (se aplican las mismas reglas: solapamiento, saldo de vacaciones, tramos por horas). Para una ausencia por horas envía start_time y end_time (mismo día). Devuelve la ficha de la ausencia creada (201). Requiere que la clave pueda administrar empleados (permiso de empleados).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"Empleado al que pertenece la ausencia."},"type":{"type":"string","description":"Clave del tipo de ausencia (ver absence-types.list)."},"start_date":{"type":"string","description":"Primer día (YYYY-MM-DD). No puede ser pasado."},"end_date":{"type":"string","description":"Último día (YYYY-MM-DD), incluido."},"start_time":{"type":"string","description":"Solo por horas: hora de inicio (HH:MM), mismo día."},"end_time":{"type":"string","description":"Solo por horas: hora de fin (HH:MM)."},"note":{"type":"string","description":"Nota opcional para la solicitud."}},"required":["user_id","type","start_date","end_date"]},"example":{"user_id":"u-delm","type":"vacation","start_date":"2026-08-10","end_date":"2026-08-14"}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"example":{"data":{"id":"abs-nueva","user_id":"u-delm","type":{"key":"vacation","label":"Vacaciones"},"status":"pending","start_date":"2026-08-10","end_date":"2026-08-14","start_time":null,"end_time":null,"created_at":"2026-07-12T10:00:00.000Z","decided_at":null}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/absences/{absence_id}":{"get":{"operationId":"absences.get","summary":"Leer ausencia","description":"Devuelve la ficha de una ausencia concreta por su id: su tipo (clave + etiqueta), estado, fechas y, si es por horas, el tramo. Una ausencia de otra empresa o inexistente devuelve 404.","parameters":[{"name":"absence_id","in":"path","required":true,"description":"Identificador de la ausencia dentro de tu empresa.","schema":{"type":"string"},"example":"abs-008"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"abs-008","user_id":"u-delm","type":{"key":"vacation","label":"Vacaciones"},"status":"approved","start_date":"2026-06-22","end_date":"2026-06-26","start_time":null,"end_time":null,"created_at":"2026-06-08T00:00:00Z","decided_at":"2026-06-09T09:00:00Z"}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/absences/{absence_id}/decide":{"post":{"operationId":"absences.decide","summary":"Aprobar o rechazar ausencia","description":"Aprueba o rechaza una ausencia pendiente. El usuario de la clave debe poder aprobarla (pertenecer al departamento de RRHH o supervisar, por jerarquía, al solicitante) y no puede aprobar las suyas. Solo se puede decidir una ausencia que siga pendiente (si no, 409). Devuelve la ficha ya resuelta.","parameters":[{"name":"absence_id","in":"path","required":true,"description":"Identificador de la ausencia a resolver.","schema":{"type":"string"},"example":"abs-nueva"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","description":"\"approved\" o \"rejected\"."},"note":{"type":"string","description":"Nota opcional de la decisión."}},"required":["decision"]},"example":{"decision":"approved"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"abs-nueva","user_id":"u-delm","type":{"key":"vacation","label":"Vacaciones"},"status":"approved","start_date":"2026-08-10","end_date":"2026-08-14","start_time":null,"end_time":null,"created_at":"2026-07-12T10:00:00.000Z","decided_at":"2026-07-12T11:00:00.000Z"}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/expenses/{expense_id}":{"get":{"operationId":"expenses.get","summary":"Leer gasto","description":"Devuelve la ficha de un gasto concreto por su id: comercio, importe (con su equivalente en euros), impuestos, categoría, estado y si está conciliado con un movimiento bancario. Un gasto de otra empresa o inexistente devuelve 404.","parameters":[{"name":"expense_id","in":"path","required":true,"description":"Identificador del gasto dentro de tu empresa.","schema":{"type":"string"},"example":"exp-dem-1"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"exp-dem-1","user_id":"u-civm1","status":"imported","merchant":"Mercadona","expense_date":"2026-06-01","total_amount":23.4,"tax_amount":2.13,"currency":"EUR","amount_eur":23.4,"fx_rate":1,"category":"Dietas","reconciled":true,"error_reason":null,"created_at":"2026-06-01T12:00:00Z","processed_at":"2026-06-01T12:01:00Z"}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/absence-types":{"get":{"operationId":"absence-types.list","summary":"Listar tipos de ausencia","description":"Devuelve el catálogo de tipos de ausencia de tu empresa: la clave (`key`) que aparece en cada ausencia y los atributos que la interpretan — si es retribuido, si consume vacaciones, si requiere aprobación o justificante, si admite tramos por horas y cómo cuenta los días (`working`/`natural`). Incluye los tipos retirados (`active: false`) para que una clave usada en una ausencia antigua siempre se resuelva. La respuesta se pagina e incluye un objeto meta.","parameters":[{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Tipos por página (máx. 100). El catálogo es pequeño.","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"key":"vacation","label":"Vacaciones","paid":true,"consumes_vacation":true,"requires_approval":true,"requires_justification":false,"allows_hourly":false,"day_count_mode":"working","active":true},{"key":"justified","label":"Ausencia justificada","paid":true,"consumes_vacation":false,"requires_approval":true,"requires_justification":false,"allows_hourly":true,"day_count_mode":"working","active":true},{"key":"sick","label":"Baja por enfermedad común","paid":true,"consumes_vacation":false,"requires_approval":true,"requires_justification":true,"allows_hourly":false,"day_count_mode":"natural","active":true}],"meta":{"page":1,"page_size":3,"total":8}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/holidays":{"get":{"operationId":"holidays.list","summary":"Listar festivos","description":"Devuelve el calendario de festivos de una sede (por su id), del más antiguo al más reciente. Cada festivo trae su fecha, nombre y tipo (nacional, autonómico o local). El parámetro `work_center_id` es obligatorio (el calendario es por sede); puedes acotar por `year`. La respuesta se pagina e incluye un objeto meta.","parameters":[{"name":"work_center_id","in":"query","required":true,"description":"Id de la sede cuyo calendario quieres. Descúbrelo con \"Listar sedes\".","schema":{"type":"string"},"example":"wc-madrid"},{"name":"year","in":"query","required":false,"description":"Acota a un año (YYYY). Omítelo para todo el calendario.","schema":{"type":"string"},"example":"2026"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Festivos por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"date":"2026-01-01","name":"Año Nuevo","type":"national"},{"date":"2026-01-06","name":"Epifanía del Señor","type":"national"},{"date":"2026-04-03","name":"Viernes Santo","type":"national"}],"meta":{"page":1,"page_size":3,"total":12}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/departments":{"get":{"operationId":"departments.list","summary":"Listar departamentos","description":"Devuelve los departamentos de tu empresa, en orden alfabético, cada uno con su número de empleados y su departamento supervisor —el que tiene por encima en el organigrama (null si es raíz)—. La respuesta se pagina (25 por página, máximo 100) e incluye un objeto meta con page, page_size y total.","parameters":[{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Departamentos por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"dept-admin","name":"Administración y Finanzas","employee_count":2,"supervisor":{"id":"dept-dir","name":"Dirección General"},"created_at":"2015-01-01T00:00:00Z"},{"id":"dept-delc","name":"Delegación A Coruña","employee_count":1,"supervisor":{"id":"dept-dir","name":"Dirección General"},"created_at":"2015-01-01T00:00:00Z"},{"id":"dept-delm","name":"Delegación Madrid","employee_count":1,"supervisor":{"id":"dept-dir","name":"Dirección General"},"created_at":"2015-01-01T00:00:00Z"}],"meta":{"page":1,"page_size":3,"total":11}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/work-centers":{"get":{"operationId":"work-centers.list","summary":"Listar sedes","description":"Devuelve las sedes (centros de trabajo) de tu empresa, en orden alfabético, cada una con su ubicación: país y —cuando están definidos— comunidad, provincia y municipio, cada nivel con su código interno y su nombre. La respuesta se pagina (25 por página, máximo 100) e incluye un objeto meta con page, page_size y total.","parameters":[{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Sedes por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"wc-coruna","name":"A Coruña – Delegación","country":"es","region":{"code":"gal","name":"Galicia"},"province":{"code":"a-coruna","name":"A Coruña"},"municipality":{"code":"coruna-a","name":"A Coruña"},"created_at":"2015-01-01T00:00:00Z"},{"id":"wc-madrid","name":"Madrid – Sede central","country":"es","region":{"code":"mad","name":"Comunidad de Madrid"},"province":{"code":"madrid","name":"Madrid"},"municipality":{"code":"madrid","name":"Madrid"},"created_at":"2015-01-01T00:00:00Z"}],"meta":{"page":1,"page_size":25,"total":2}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/expenses":{"get":{"operationId":"expenses.list","summary":"Listar gastos","description":"Devuelve los gastos (tickets con OCR) de tu empresa, del más reciente al más antiguo. Cada gasto trae su comercio, importe (con su equivalente en euros), impuestos, categoría, estado (processing / imported / error) y si está conciliado con un movimiento bancario. Filtrable por empleado y estado. Se pagina (25 por página, máximo 100) e incluye un objeto meta con page, page_size y total.","parameters":[{"name":"user_id","in":"query","required":false,"description":"Solo los gastos de este empleado.","schema":{"type":"string"},"example":"u-civm1"},{"name":"status","in":"query","required":false,"description":"Estado: 'processing', 'imported' o 'error'.","schema":{"type":"string"},"example":"imported"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Gastos por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"exp-dem-4","user_id":"u-civm1","status":"imported","merchant":"Taxi Madrid","expense_date":"2026-06-05","total_amount":18,"tax_amount":1.64,"currency":"EUR","amount_eur":18,"fx_rate":1,"category":"Desplazamiento","reconciled":false,"error_reason":null,"created_at":"2026-06-05T09:30:00Z","processed_at":"2026-06-05T09:31:00Z"},{"id":"exp-dem-1","user_id":"u-civm1","status":"imported","merchant":"Mercadona","expense_date":"2026-06-01","total_amount":23.4,"tax_amount":2.13,"currency":"EUR","amount_eur":23.4,"fx_rate":1,"category":"Dietas","reconciled":true,"error_reason":null,"created_at":"2026-06-01T12:00:00Z","processed_at":"2026-06-01T12:01:00Z"}],"meta":{"page":1,"page_size":25,"total":2}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/projects":{"get":{"operationId":"projects.list","summary":"Listar proyectos","description":"Devuelve el catálogo de proyectos y actividades internas de la empresa, de más reciente a más antiguo. El `code` (PRJ-0001 para proyectos, INT-0001 para actividades internas) es el identificador estable con el que casar tus propios ids: no cambia nunca. Un proyecto archivado (`status: inactive`) conserva su histórico pero ya no admite imputaciones nuevas.","parameters":[{"name":"status","in":"query","required":false,"description":"Filtra por estado: 'active' o 'inactive'.","schema":{"type":"string"},"example":"active"},{"name":"kind","in":"query","required":false,"description":"Filtra por tipo: 'project' (trabajo real) o 'internal' (actividad interna).","schema":{"type":"string"},"example":"project"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Proyectos por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"tenant-001:prj-1","code":"PRJ-0001","name":"Puente sobre el Miño","description":"Proyecto estructural para la Xunta de Galicia","kind":"project","status":"active","created_at":"2026-06-01T00:00:00Z"},{"id":"tenant-001:prj-2","code":"PRJ-0002","name":"Nave logística ACME","description":"Cálculo y dirección de obra de nave industrial en Getafe","kind":"project","status":"active","created_at":"2026-06-01T00:00:00Z"}],"meta":{"page":1,"page_size":2,"total":3}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/projects/{project_id}":{"get":{"operationId":"projects.get","summary":"Leer proyecto","description":"Ficha de un proyecto o actividad interna del catálogo. Un id de otra empresa y uno inexistente devuelven ambos 404, sin distinguirlos.","parameters":[{"name":"project_id","in":"path","required":true,"description":"Id del proyecto en tu empresa.","schema":{"type":"string"},"example":"tenant-001:prj-2"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"id":"tenant-001:prj-2","code":"PRJ-0002","name":"Nave logística ACME","description":"Cálculo y dirección de obra de nave industrial en Getafe","kind":"project","status":"active","created_at":"2026-06-01T00:00:00Z"}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}},"/api/v1/allocations":{"get":{"operationId":"allocations.list","summary":"Listar imputaciones","description":"Devuelve a qué proyecto fue cada tramo de tiempo trabajado en el rango (ambos extremos incluidos, máximo 366 días), con su duración en minutos y las observaciones de quien lo imputó. `project` es null cuando el tramo fue a «otras tareas», y entonces la descripción va en `note`: siempre hay uno de los dos. Es información de gestión y no altera el registro de jornada.","parameters":[{"name":"from","in":"query","required":true,"description":"Primer día del rango (YYYY-MM-DD).","schema":{"type":"string"},"example":"2026-06-02"},{"name":"to","in":"query","required":true,"description":"Último día del rango (YYYY-MM-DD), incluido.","schema":{"type":"string"},"example":"2026-06-02"},{"name":"user_id","in":"query","required":false,"description":"Solo las imputaciones de este empleado.","schema":{"type":"string"},"example":"u-delm"},{"name":"project_id","in":"query","required":false,"description":"Solo las imputaciones a este proyecto.","schema":{"type":"string"},"example":"tenant-001:prj-2"},{"name":"tz","in":"query","required":false,"description":"Zona horaria IANA con la que se agrupan los días.","schema":{"type":"string"},"example":"Europe/Madrid"},{"name":"page","in":"query","required":false,"description":"Página (desde 1).","schema":{"type":"integer"},"example":"1"},{"name":"page_size","in":"query","required":false,"description":"Imputaciones por página (máx. 100).","schema":{"type":"integer"},"example":"25"}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"alloc-delm-0602m","user_id":"u-delm","date":"2026-06-02","start":"2026-06-02T07:00:00.000Z","end":"2026-06-02T12:00:00.000Z","minutes":300,"project":{"id":"tenant-001:prj-2","code":"PRJ-0002","name":"Nave logística ACME"},"note":null,"comment":null,"source":"auto","created_at":"2026-06-02T12:00:00.000Z"},{"id":"alloc-delm-0602t1","user_id":"u-delm","date":"2026-06-02","start":"2026-06-02T13:00:00.000Z","end":"2026-06-02T16:00:00.000Z","minutes":180,"project":{"id":"tenant-001:prj-2","code":"PRJ-0002","name":"Nave logística ACME"},"note":null,"comment":null,"source":"auto","created_at":"2026-06-02T16:00:00.000Z"},{"id":"alloc-delm-0602t2","user_id":"u-delm","date":"2026-06-02","start":"2026-06-02T16:00:00.000Z","end":"2026-06-02T17:30:00.000Z","minutes":90,"project":{"id":"tenant-001:int-2","code":"INT-0002","name":"Soporte a compañero"},"note":null,"comment":"Ayuda a Elena con el cálculo de la viga carril","source":"manual","created_at":"2026-06-02T17:30:00.000Z"}],"meta":{"page":1,"page_size":25,"total":3}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}},"post":{"operationId":"allocations.create","summary":"Imputar horas","description":"Imputa un tramo de tiempo de un empleado a un proyecto (`project_id`) o a «otras tareas` (`note`), nunca a ambos. `comment` añade observaciones opcionales sobre qué se hizo. No toca los fichajes del empleado: la imputación es una capa de gestión sobre el registro de jornada, así que no puede alterarlo.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"Empleado cuyo tiempo se imputa."},"start":{"type":"string","description":"Inicio del tramo (ISO-8601 UTC)."},"end":{"type":"string","description":"Fin del tramo (ISO-8601 UTC). Posterior al inicio."},"project_id":{"type":"string","description":"Proyecto del catálogo al que fue el tiempo. Excluyente con `note`."},"note":{"type":"string","description":"Descripción libre para «otras tareas». Excluyente con `project_id`."},"comment":{"type":"string","description":"Observaciones sobre qué se hizo en ese tramo."},"tz":{"type":"string","description":"Zona horaria IANA con la que se deduce el día local."}},"required":["user_id","start","end"]},"example":{"user_id":"u-delm","start":"2026-06-04T07:00:00.000Z","end":"2026-06-04T12:00:00.000Z","project_id":"tenant-001:prj-2","comment":"Mediciones de la cubierta"}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"example":{"data":{"id":"eee013bc-85a0-4c4b-8442-bc8223e56a55","user_id":"u-delm","date":"2026-06-04","start":"2026-06-04T07:00:00.000Z","end":"2026-06-04T12:00:00.000Z","minutes":300,"project":{"id":"tenant-001:prj-2","code":"PRJ-0002","name":"Nave logística ACME"},"note":null,"comment":"Mediciones de la cubierta","source":"manual","created_at":"2026-07-19T07:12:25.455Z"}}}}},"400":{"description":"Petición inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"401":{"description":"Clave de API ausente o inválida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"403":{"description":"La clave no tiene habilitado este método","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"404":{"description":"Recurso no encontrado","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"409":{"description":"Conflicto (p. ej. email ya en uso)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}},"429":{"description":"Demasiadas peticiones (límite: 300 por minuto y clave). La respuesta trae la cabecera Retry-After con los segundos que hay que esperar; los SDKs oficiales reintentan solos.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","description":"Campos concretos que fallaron, cuando el error es de validación.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["code","message"]}}}}}}}}}}}