# MiJornada API > Public REST API of MiJornada, time-tracking software for companies in Spain. It reads working days, clock events, absences and employees, sends signed webhooks, invites employees and records absences. It cannot create or change clock events. Base URL: https://api.mijornada.app/v1 Human documentation: https://mijornada.app/en/developers OpenAPI contract: https://mijornada.app/openapi.yaml Included in every plan, including the free one. ## Authentication Send `Authorization: Bearer mj_live_…` on every request. The company owner creates keys in Company > Integrations. A key belongs to one company and sees only that company. ## Conventions - Lists return `{ "data": [...], "nextCursor": null }`. Pass `nextCursor` back as `cursor`. - Instants are in UTC with a `Z` suffix. `dayKey` is the working day in the company's time zone. - Errors use `application/problem+json` with `code`, `detail` and `requestId`. - 120 requests per minute per key. Above that, 429 with `Retry-After`. - `/workdays` has the figures in force, with approved corrections applied. Use it for payroll. `/time-events` is the raw record. - Medical leave appears as a plain absence, with no reason or documents. ## Account ### GET /me Check a key Returns the company and the key. Use it to test a connection. Response: - `apiVersion` (v1) - `organization` (object) - `key` (object) Success status: 200. Errors: 401, 429. ## Employees and worksites ### GET /employees List employees Requires the `employees:read` scope. Parameters: - `email` (query, string (email)): Exact email address. - `employeeCode` (query, string): The code the company gave the employee. - `worksiteId` (query, string) - `status` (query, ACTIVE | ENDED) - `limit` (query, integer) - `cursor` (query, string): The `nextCursor` of the previous page. Each item in `data`: - `id` (string) - `displayName` (string): The full name as written in MiJornada. - `firstName` (string): First word of the name. An estimate. - `lastName` (string): The rest of the name. An estimate. - `email` (string (email) | null) - `employeeCode` (string | null) - `worksiteId` (string | null) - `status` (ACTIVE | ENDED) - `employmentStartDate` (string (date) | null) - `createdAt` (string (date-time) | null) - `updatedAt` (string (date-time)) Success status: 200. Errors: 401, 403, 422, 429. ### GET /employees/{id} Get an employee Requires the `employees:read` scope. Parameters: - `id` (path, string, required) Response: - `id` (string) - `displayName` (string): The full name as written in MiJornada. - `firstName` (string): First word of the name. An estimate. - `lastName` (string): The rest of the name. An estimate. - `email` (string (email) | null) - `employeeCode` (string | null) - `worksiteId` (string | null) - `status` (ACTIVE | ENDED) - `employmentStartDate` (string (date) | null) - `createdAt` (string (date-time) | null) - `updatedAt` (string (date-time)) Success status: 200. Errors: 401, 403, 404, 429. ### GET /worksites List worksites Requires the `employees:read` scope. Each item in `data`: - `id` (string) - `name` (string) - `address` (string) - `legalName` (string | null) - `taxId` (string | null) Success status: 200. Errors: 401, 403, 429. ### POST /invitations Invite an employee Emails the person a code to join the company. The code is never returned here: it is what lets someone in. Seats, duplicates and the plan limit work as in MiJornada. At most 60 invitations per hour and key. Requires the `invitations:write` scope. Parameters: - `Idempotency-Key` (header, string): Repeat a request with the same value and it runs once. Kept for 24 hours. Body: - `fullName` (string, required) - `email` (string (email), required) - `employmentStartDate` (string (date), required): Cannot be in the future. - `locale` (es | en) - `worksiteId` (string) Response: - `id` (string) - `displayName` (string) - `email` (string (email)) - `locale` (string) - `worksiteId` (string | null) - `employmentStartDate` (string (date)) - `expiresAt` (string (date-time)) - `deliveryStatus` (PENDING) Success status: 201. Errors: 401, 403, 409, 422, 429. ## Working days and clock events ### GET /workdays List working days One row per person and day, with approved corrections already applied. These are the figures shown in MiJornada and in the inspection report, so this is the endpoint to use for payroll. Requires the `time:read` scope. Parameters: - `from` (query, string (date), required): First day, in the company's time zone. - `to` (query, string (date), required): Last day, included. At most 93 days after `from`, or a year for absences. - `employeeId` (query, string) - `include` (query, timeline): Pass `timeline` to add the day's movements. - `limit` (query, integer) - `cursor` (query, string): The `nextCursor` of the previous page. Each item in `data`: - `id` (string) - `employeeId` (string) - `employeeName` (string) - `employeeCode` (string | null) - `dayKey` (string (date)) - `workedMinutes` (integer): Minutes that count as worked, including paid breaks. - `breakMinutes` (integer) - `paidBreakMinutes` (integer) - `breakOverAllowanceMinutes` (integer) - `firstInAt` (string (date-time) | null) - `lastOutAt` (string (date-time) | null) - `lastAction` (NONE | IN | BREAK_START | BREAK_END | OUT) - `state` (string) - `corrected` (boolean): True when an approved correction applies to this day. - `timeline` (object[]): Only with `include=timeline`. Success status: 200. Errors: 401, 403, 422, 429. ### GET /time-events List clock events The raw record of clock events, oldest first. Approved corrections do **not** change these rows: use `/workdays` for the figures in force. A clock event appears here about 90 seconds after it is made, once it can no longer be undone. Requires the `time:read` scope. Parameters: - `from` (query, string (date), required): First day, in the company's time zone. - `to` (query, string (date), required): Last day, included. At most 93 days after `from`, or a year for absences. - `employeeId` (query, string) - `source` (query, MOBILE | KIOSK | ADMIN | IMPORT | SYSTEM) - `limit` (query, integer) - `cursor` (query, string): The `nextCursor` of the previous page. Each item in `data`: - `id` (string) - `employeeId` (string) - `employeeName` (string) - `employeeCode` (string | null) - `action` (IN | BREAK_START | BREAK_END | OUT) - `source` (string) - `clockingMethod` (string | null) - `occurredAt` (string (date-time)): When it happened. An offline clock event keeps its original time. - `receivedAt` (string (date-time) | null): When the server received it. Empty for events older than this field. - `dayKey` (string (date)) - `worksiteId` (string | null) - `autoClosed` (boolean): True when MiJornada closed a forgotten shift. Success status: 200. Errors: 401, 403, 422, 429. ## Absences ### GET /absences List absences Absences that overlap the range, for up to a year. Medical leave appears as a plain `ABSENCE`, with no reason or documents. Requires the `absences:read` scope. Parameters: - `from` (query, string (date), required): First day, in the company's time zone. - `to` (query, string (date), required): Last day, included. At most 93 days after `from`, or a year for absences. - `employeeId` (query, string) - `status` (query, REQUESTED | APPROVED | REJECTED | CANCELLED) - `limit` (query, integer) - `cursor` (query, string): The `nextCursor` of the previous page. Each item in `data`: - `id` (string) - `employeeId` (string) - `employeeName` (string) - `employeeCode` (string | null) - `type` (VACATION | ABSENCE) - `status` (REQUESTED | APPROVED | REJECTED | CANCELLED) - `startDate` (string (date)) - `endDate` (string (date)) - `requestedDays` (number | null) - `requestedHours` (number | null) - `createdAt` (string (date-time)) - `updatedAt` (string (date-time)) Success status: 200. Errors: 401, 403, 422, 429. ### POST /absences Record an absence Records an approved absence, as if the company had entered it. The same rules apply as in MiJornada: overlaps are rejected and vacation days are taken from the balance. Medical leave cannot be created here. Requires the `absences:write` scope. Parameters: - `Idempotency-Key` (header, string): Repeat a request with the same value and it runs once. Kept for 24 hours. Body: - `employeeId` (string, required) - `type` (VACATION | ABSENCE, required) - `startDate` (string (date), required) - `endDate` (string (date), required) - `reason` (string): Stored in MiJornada. Never returned by the API. Response: - `id` (string) - `employeeId` (string) - `employeeName` (string) - `employeeCode` (string | null) - `type` (VACATION | ABSENCE) - `status` (REQUESTED | APPROVED | REJECTED | CANCELLED) - `startDate` (string (date)) - `endDate` (string (date)) - `requestedDays` (number | null) - `requestedHours` (number | null) - `createdAt` (string (date-time)) - `updatedAt` (string (date-time)) Success status: 201. Errors: 401, 403, 404, 409, 422, 429. ## Events and webhooks ### GET /events List recent events Recent events of one type, newest first, in exactly the shape webhooks deliver. Requires the scope that reads that data. Parameters: - `type` (query, time_event.recorded | time_import.committed | workday.closed | absence.requested | absence.approved | absence.rejected | absence.cancelled | correction.approved | correction.rejected | employee.joined, required) - `since` (query, string (date-time)): Only events from this moment on. - `limit` (query, integer) - `cursor` (query, string): The `nextCursor` of the previous page. Each item in `data`: - `id` (string): Stable. The same event always has the same id. - `type` (time_event.recorded | time_import.committed | workday.closed | absence.requested | absence.approved | absence.rejected | absence.cancelled | correction.approved | correction.rejected | employee.joined) - `apiVersion` (v1) - `occurredAt` (string (date-time)) - `data` (object): The same object the matching list endpoint returns. Success status: 200. Errors: 401, 403, 422, 429. ### GET /webhooks List webhooks Requires the `webhooks:manage` scope. Each item in `data`: - `id` (string) - `targetUrl` (string (uri)) - `events` (time_event.recorded | time_import.committed | workday.closed | absence.requested | absence.approved | absence.rejected | absence.cancelled | correction.approved | correction.rejected | employee.joined[]) - `filters` (object): Only send matching events. Applies to `time_event.recorded`. - `apiVersion` (string) - `status` (ACTIVE | DISABLED) - `disabledReason` (KEY_REVOKED | TARGET_GONE | TOO_MANY_FAILURES) - `lastSuccessAt` (string (date-time) | null) - `createdAt` (string (date-time)) Success status: 200. Errors: 401, 403, 429. ### POST /webhooks Create a webhook MiJornada sends a signed POST to `targetUrl` for each event, about two minutes after it happens. A clock event is held 90 seconds because it can still be undone. Each delivery carries `X-MiJornada-Signature: t=,v1=`, the HMAC-SHA256 of `.` with the `secret` returned here. It is shown only once. Failed deliveries are retried up to 12 times over about a day. Answering 410 stops the webhook. So do 20 failures in a row and three days without a success, and the company owner is told. Requires the `webhooks:manage` scope, plus the scope that reads each event. Body: - `targetUrl` (string (uri), required): A public https URL. - `events` (time_event.recorded | time_import.committed | workday.closed | absence.requested | absence.approved | absence.rejected | absence.cancelled | correction.approved | correction.rejected | employee.joined[], required) - `filters` (object): Only send matching events. Applies to `time_event.recorded`. Response: - `id` (string) - `targetUrl` (string (uri)) - `events` (time_event.recorded | time_import.committed | workday.closed | absence.requested | absence.approved | absence.rejected | absence.cancelled | correction.approved | correction.rejected | employee.joined[]) - `filters` (object): Only send matching events. Applies to `time_event.recorded`. - `apiVersion` (string) - `status` (ACTIVE | DISABLED) - `disabledReason` (KEY_REVOKED | TARGET_GONE | TOO_MANY_FAILURES) - `lastSuccessAt` (string (date-time) | null) - `createdAt` (string (date-time)) - `secret` (string) Success status: 201. Errors: 401, 403, 409, 422, 429. ### DELETE /webhooks/{id} Delete a webhook Deleting one that no longer exists also returns 204. Parameters: - `id` (path, string, required) Success status: 204. Errors: 401, 403, 429.