MASKDocs

Integration API API reference

Events

Clock events from the terminals, from administrators and from API keys, with their corrections and voids. Reads need events:read, writes events:write.

Clock events are the record MASK keeps: every tap on a terminal, every manual entry and every event a key writes. Events are never edited or deleted. A correction marks the original corrected and adds a new event whose correctsEventId names it; a void marks it voided.

An event has id, employeeId, institutionId, businessDate, eventCode, eventTime, source (manual from the web app, controller from a terminal, api from a key, portal, mobile), status (active, corrected, voided), correctsEventId, anomalies[] (stale_closure, duplicate, no_open_session, out_of_sequence; empty when clean), note, createdAt and updatedAt.

MASK recomputes the anomalies of a person's events after every write to their shifts, so read an event again for its current flags: a clock_in answered with stale_closure loses it once its clock_out arrives.

List events#

GET/integration/eventsScope: events:read

Clock events between two business dates (inclusive, at most 31 days; each institution's own timezone and day boundary), oldest first (eventTime, id). 403 institution_not_allowed for an institutionId outside the key.

status=all adds the corrected and voided rows, so a mirror can apply corrections (see Mirroring events).

curl -s "$MASK/integration/events?from=2026-11-03&to=2026-11-04&employeeId=24&status=all" -H "Authorization: Bearer $KEY"
{
  "list": [
    {"id": 7607, "employeeId": 24, "institutionId": 1, "businessDate": "2026-11-03", "eventCode": "clock_in",
     "eventTime": 1793689200, "source": "api", "status": "active", "anomalies": [], "createdAt": 1793812944},
    {"id": 7608, "employeeId": 24, "institutionId": 1, "businessDate": "2026-11-03", "eventCode": "clock_out",
     "eventTime": 1793718900, "source": "api", "status": "corrected", "anomalies": [], "createdAt": 1793812944, "updatedAt": 1793812951},
    {"id": 7609, "employeeId": 24, "institutionId": 1, "businessDate": "2026-11-03", "eventCode": "clock_out",
     "eventTime": 1793719800, "source": "api", "status": "active", "correctsEventId": 7608, "anomalies": [],
     "note": "Corrected by ERP: left at 16:30", "createdAt": 1793812951},
    {"id": 7605, "employeeId": 24, "institutionId": 1, "businessDate": "2026-11-04", "eventCode": "clock_in",
     "eventTime": 1793802122, "source": "api", "status": "voided", "anomalies": [], "note": "Gate 2 (ERP event 88123)",
     "createdAt": 1793812922, "updatedAt": 1793812958},
    {"id": 7606, "employeeId": 24, "institutionId": 1, "businessDate": "2026-11-04", "eventCode": "clock_out",
     "eventTime": 1793812871, "source": "api", "status": "voided", "anomalies": [], "createdAt": 1793812931, "updatedAt": 1793812958}
  ],
  "count": 5,
  "page": 1,
  "pageSize": 50,
  "totalPages": 1
}

An institutionId outside a key that lists institutions:

{"status": "error", "code": "institution_not_allowed", "message": "Access denied to institution 4", "requestId": "84eb63ae-a444-47c0-a24c-ae400825993c"}

Parameters

  • fromquerydaterequired

    First business date, ISO yyyy-MM-dd, from 2000-01-01 to 2037-12-31

  • toquerydaterequired

    Last business date, ISO yyyy-MM-dd, from 2000-01-01 to 2037-12-31; from..to spans at most 31 days, both included

  • institutionIdqueryinteger

    One of the key's institutions; omitted = all of them

  • employeeIdqueryinteger

    Only this person's events; a person the key cannot see gives an empty list

  • statusquerystring

    active | all (adds corrected and voided rows, so a mirror can apply corrections)

    default "active"

  • pagequeryinteger

    1-based

    default 1

  • pageSizequeryinteger

    1-200

    default 50

Responses

200OKEvent page
Fields of Event page
  • countinteger
  • listarray of Event
    Fields of Event
    • anomaliesarray of string

      Rollup flags, e.g. duplicate, no_open_session; empty when clean

    • businessDatedate

      The business date at the institution (local time minus its day boundary), ISO date

    • correctsEventIdinteger

      For a correction: the id of the event it replaces

    • createdAtinteger
    • employeeIdinteger
    • eventCodestring
    • eventTimeinteger

      Epoch seconds, UTC

    • idinteger
    • institutionIdinteger
    • notestring
    • sourcestring

      manual, controller, api, portal or mobile

    • statusstring

      active, corrected or voided

    • updatedAtinteger
  • pageinteger
  • pageSizeinteger
  • totalPagesinteger
Errors this route can answer
  • 400Bad Requestvalidation_failedidempotency_requiredevent_too_old
  • 401Unauthorizedinvalid_api_keykey_expiredkey_revoked
  • 403Forbiddenip_not_allowedscope_missinginstitution_not_allowednot_api_eventadmin_linked_employee
  • 429Too Many Requestsrate_limited
  • 500Internal Server Errorinternal_error
  • 503Service Unavailableunavailable

Each error has the same JSON body; see Errors for what every code means.

Get event#

GET/integration/events/{id}Scope: events:read

One clock event, whatever its status; 404 when unknown or outside the key's institutions.

Use it to read the event a correction's correctsEventId names: corrected and voided events stay readable by id.

Parameters

  • idpathintegerrequired

Responses

200OKEvent
Fields of Event
  • anomaliesarray of string

    Rollup flags, e.g. duplicate, no_open_session; empty when clean

  • businessDatedate

    The business date at the institution (local time minus its day boundary), ISO date

  • correctsEventIdinteger

    For a correction: the id of the event it replaces

  • createdAtinteger
  • employeeIdinteger
  • eventCodestring
  • eventTimeinteger

    Epoch seconds, UTC

  • idinteger
  • institutionIdinteger
  • notestring
  • sourcestring

    manual, controller, api, portal or mobile

  • statusstring

    active, corrected or voided

  • updatedAtinteger
Errors this route can answer
  • 400Bad Requestvalidation_failedidempotency_requiredevent_too_old
  • 401Unauthorizedinvalid_api_keykey_expiredkey_revoked
  • 403Forbiddenip_not_allowedscope_missinginstitution_not_allowednot_api_eventadmin_linked_employee
  • 404Not Foundnot_found
  • 429Too Many Requestsrate_limited
  • 500Internal Server Errorinternal_error
  • 503Service Unavailableunavailable

Each error has the same JSON body; see Errors for what every code means.

Create event#

POST/integration/eventsScope: events:write

Record a clock event (source api) for an employee at an institution of the key; the Idempotency-Key header is required.

The event is attributed to the key. MASK files it into the person's shift at once, by the same rules as a card tap: a clock_in opens a shift and the next clock_out closes it.

Refusals: an institution outside the key 403; a person the key cannot see 404; an archived institution 409; an unknown code, a code not enabled there, a person who is not a member there or whose membership ended before the event's business date, or an eventTime more than 300 s ahead: 400 validation_failed; a business date more than 62 days back: 400 event_too_old.

curl -s -X POST "$MASK/integration/events" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: erp-clock-88101' \
  -d '{"employeeId":24,"institutionId":1,"eventCode":"clock_in","eventTime":1793689200}'

201 Created:

{
  "id": 7607,
  "employeeId": 24,
  "institutionId": 1,
  "businessDate": "2026-11-03",
  "eventCode": "clock_in",
  "eventTime": 1793689200,
  "source": "api",
  "status": "active",
  "anomalies": ["stale_closure"],
  "createdAt": 1793812944
}

stale_closure, because the shift opened at 08:00 the day before had no clock-out yet when this answer was written; it cleared when the clock-out arrived. Some refusals:

{"status": "error", "code": "validation_failed", "message": "Event code break_start is not enabled for institution 1", "requestId": "6ce3c4f0-947f-47de-bafb-1a6db37a6958"}
{"status": "error", "code": "validation_failed", "message": "Employee 24's membership at institution 4 ended on 2026-11-03", "requestId": "55a6b10a-51b6-4c5e-bf4e-d7ee94e4194e"}
{"status": "error", "code": "event_too_old", "message": "eventTime lies on business date 2026-08-20, more than 62 days back; an API key cannot write that far back", "requestId": "a0a1180c-ef95-45eb-89fd-977f6b745285"}
{"status": "error", "code": "idempotency_required", "message": "This endpoint requires an Idempotency-Key header", "requestId": "c86a6c29-1e6e-494a-956d-fa2ac98618ba"}

Parameters

  • Idempotency-Keyheaderstringrequired

    Required. 1 to 128 visible ASCII characters. A retry with the same key and request within 24 h gets the first reply back (Idempotent-Replayed: true) instead of writing again

Request body

JSON, EventCreateRequest. Fields not listed here are ignored.

  • employeeIdintegerrequired
  • eventCodestringrequired

    A code from GET /integration/event-codes that is enabled at the institution

    at most 50 characters

  • eventTimeintegerrequired

    Epoch seconds, UTC; at most 300 s in the future and at most 62 days back (business date)

  • institutionIdintegerrequired
  • notestring

    at most 500 characters

Responses

201The event as storedEvent
Fields of Event
  • anomaliesarray of string

    Rollup flags, e.g. duplicate, no_open_session; empty when clean

  • businessDatedate

    The business date at the institution (local time minus its day boundary), ISO date

  • correctsEventIdinteger

    For a correction: the id of the event it replaces

  • createdAtinteger
  • employeeIdinteger
  • eventCodestring
  • eventTimeinteger

    Epoch seconds, UTC

  • idinteger
  • institutionIdinteger
  • notestring
  • sourcestring

    manual, controller, api, portal or mobile

  • statusstring

    active, corrected or voided

  • updatedAtinteger
Errors this route can answer
  • 400Bad Requestvalidation_failedidempotency_requiredevent_too_old
  • 401Unauthorizedinvalid_api_keykey_expiredkey_revoked
  • 403Forbiddenip_not_allowedscope_missinginstitution_not_allowednot_api_eventadmin_linked_employee
  • 404Not Foundnot_found
  • 409Conflictconflictidempotency_in_progress
  • 422Unprocessable Contentidempotency_mismatch
  • 429Too Many Requestsrate_limited
  • 500Internal Server Errorinternal_error
  • 503Service Unavailableunavailable

Each error has the same JSON body; see Errors for what every code means.

Correct event#

PATCH/integration/events/{id}Scope: events:write

Correct an event the integration API created: a new api event replaces it (correctsEventId = id), the original becomes corrected.

Any key of the same MASK system may correct an event with source: "api". Send eventCode, eventTime or note, at least one; omitted ones keep the original's value. The answer is the new event.

  • Correct the newest event of a chain: a corrected or voided event is 409 conflict.
  • Any other source is 403 not_api_event. Once an administrator has corrected an API event in the web app, the replacement is manual and out of the key's reach.
  • The original's business date and the new eventTime must both lie within 62 days (400 event_too_old).
curl -s -X PATCH "$MASK/integration/events/7608" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: erp-fix-88102-1' \
  -d '{"eventTime":1793719800,"note":"Corrected by ERP: left at 16:30"}'

200 OK:

{
  "id": 7609,
  "employeeId": 24,
  "institutionId": 1,
  "businessDate": "2026-11-03",
  "eventCode": "clock_out",
  "eventTime": 1793719800,
  "source": "api",
  "status": "active",
  "correctsEventId": 7608,
  "anomalies": [],
  "note": "Corrected by ERP: left at 16:30",
  "createdAt": 1793812951
}
{"status": "error", "code": "not_api_event", "message": "Only events created through the integration API can be corrected; this one has source controller", "requestId": "7134a7cb-42fb-42ae-9486-f53c01358eba"}

Parameters

  • idpathintegerrequired
  • Idempotency-Keyheaderstring

    Optional. 1 to 128 visible ASCII characters. A retry with the same key and request within 24 h gets the first reply back (Idempotent-Replayed: true) instead of writing again

Request body

JSON, EventCorrectRequest. Fields not listed here are ignored.

  • eventCodestring

    at most 50 characters

  • eventTimeinteger

    Epoch seconds, UTC; at most 300 s in the future and at most 62 days back (business date)

  • notestring

    at most 500 characters

Responses

200OKEvent
Fields of Event
  • anomaliesarray of string

    Rollup flags, e.g. duplicate, no_open_session; empty when clean

  • businessDatedate

    The business date at the institution (local time minus its day boundary), ISO date

  • correctsEventIdinteger

    For a correction: the id of the event it replaces

  • createdAtinteger
  • employeeIdinteger
  • eventCodestring
  • eventTimeinteger

    Epoch seconds, UTC

  • idinteger
  • institutionIdinteger
  • notestring
  • sourcestring

    manual, controller, api, portal or mobile

  • statusstring

    active, corrected or voided

  • updatedAtinteger
Errors this route can answer
  • 400Bad Requestvalidation_failedidempotency_requiredevent_too_old
  • 401Unauthorizedinvalid_api_keykey_expiredkey_revoked
  • 403Forbiddenip_not_allowedscope_missinginstitution_not_allowednot_api_eventadmin_linked_employee
  • 404Not Foundnot_found
  • 409Conflictconflictidempotency_in_progress
  • 422Unprocessable Contentidempotency_mismatch
  • 429Too Many Requestsrate_limited
  • 500Internal Server Errorinternal_error
  • 503Service Unavailableunavailable

Each error has the same JSON body; see Errors for what every code means.

Void event#

DELETE/integration/events/{id}Scope: events:write

Void an event the integration API created.

The rules of a correction apply: source: "api", active, business date within 62 days. The answer is 204 No Content, and the shift is recomputed without the event.

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$MASK/integration/events/7606" -H "Authorization: Bearer $KEY" \
  -H 'Idempotency-Key: erp-void-88124'
204

Voiding it again, without the same Idempotency-Key:

{"status": "error", "code": "conflict", "message": "Only an active event can be voided; this one is voided", "requestId": "1cd6844c-3437-4fc4-8ab6-f109c66ee881"}

Parameters

  • idpathintegerrequired
  • Idempotency-Keyheaderstring

    Optional. 1 to 128 visible ASCII characters. A retry with the same key and request within 24 h gets the first reply back (Idempotent-Replayed: true) instead of writing again

Responses

204Voided
Errors this route can answer
  • 400Bad Requestvalidation_failedidempotency_requiredevent_too_old
  • 401Unauthorizedinvalid_api_keykey_expiredkey_revoked
  • 403Forbiddenip_not_allowedscope_missinginstitution_not_allowednot_api_eventadmin_linked_employee
  • 404Not Foundnot_found
  • 409Conflictconflictidempotency_in_progress
  • 422Unprocessable Contentidempotency_mismatch
  • 429Too Many Requestsrate_limited
  • 500Internal Server Errorinternal_error
  • 503Service Unavailableunavailable

Each error has the same JSON body; see Errors for what every code means.