Purple Time API Documentation

Machine-readable OpenAPI 3.0 specification: https://marketplace.moveworks.com/api/purple-suite/time/openapi.json

The consolidated all-apps document at https://marketplace.moveworks.com/api/purple-suite/openapi.json can be narrowed to this app with ?apps=time, or combined with other apps comma-separated (e.g. ?apps=time,itsm).

Overview

Workforce time & attendance — clock in/out, meal and rest breaks, schedules, timecards, and meal-break compliance exceptions. Resembles tools like ADP Time and Ceridian Dayforce. The signature flow is meal-break compliance: `POST /meal-compliance/evaluate` finds employees who have been on the clock past their country's meal threshold, records a `MEAL_BREAK_DUE` exception, and fires the `time.meal_break.due` webhook. Acting on it — `POST /clock` with `action: "meal_start"` — records the punch and automatically resolves that exception. **Where this sits next to Purple HRS.** Purple Time is the source of truth for hours ACTUALLY WORKED: it owns clock in/out punches, the timecards derived from them, schedules, and meal-break compliance. Purple HRS owns the payroll- and HR-facing views of time — its `/timesheets` are weekly hours summaries that feed payroll (no punches, no breaks), its `/time-off-requests` are absence requests, and it remains the system of record for PTO balances. Ask Purple Time "when did they clock in, and do they owe a break?"; ask Purple HRS "how many hours go to payroll this week, and what leave is booked?". --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Endpoints

POST /api/purple-suite/time/clock

Record a punch (clock in/out, meal, break)

The timeclock action a chat plugin calls. `employee_email` defaults to the effective user and accepts the literal "me". Punches are validated against the day's existing punches — you cannot clock in twice, clock out mid-meal, or take a second meal break — and a rejected punch returns 422 explaining why. Recording `meal_start` also resolves any open MEAL_BREAK_DUE / MISSED_MEAL exception for that employee and day, which is how the meal-break notification loop closes. Updates the open timecard's totals and today's shift status.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

  • action string requiredWhich punch to record.
  • employee_email stringEmployee to punch for. Defaults to the effective user; accepts "me".
  • timestamp stringWhen the punch happened. Defaults to now.
  • source string
  • notes string
  • shift_id stringDefaults to today's scheduled shift.
  • employee_id string

Responses

201
Punch recorded

application/json

PunchResult

  • punch object

    Punch

    • id string
    • employee_email string required
    • employee_id string
    • type string required
    • timestamp string required
    • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
    • source string
    • shift_id string
    • notes string
    • createdAt string
    • updatedAt string
  • status object
    • employee_email string
    • date string
    • clocked_in boolean
    • clock_in_time string
    • hours_worked_today number
    • on_meal boolean
    • on_break boolean
    • meal_taken_today boolean
    • threshold_hours number
    • minutes_until_meal_required integerNegative when the meal break is already overdue; null when nothing is owed.
    • meal_break_overdue boolean
    • meal_reminder_due booleanTrue inside reminder_lead_minutes of the threshold, or past it.
    • stale_session booleanTrue when the session has been open more than 16 hours — a forgotten clock-out rather than a meal-break case.
    • meal_rule object
      • country string
      • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
      • meal_duration_minutes integer
      • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
      • paid booleanPaid meals keep accruing worked time.
      • description string
    • current_shift object

      Shift

      • id string
      • employee_email string required
      • date string required
      • start_time string
      • end_time string
      • position string
      • department string
      • location string
      • status string
      • createdAt string
      • updatedAt string
    • punches array

      array of:

      Punch

      • id string
      • employee_email string required
      • employee_id string
      • type string required
      • timestamp string required
      • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
      • source string
      • shift_id string
      • notes string
      • createdAt string
      • updatedAt string
  • resolved_exceptions arrayException ids auto-resolved by this punch (a meal_start closes any open MEAL_BREAK_DUE / MISSED_MEAL for the day).
    Exception ids auto-resolved by this punch (a meal_start closes any open MEAL_BREAK_DUE / MISSED_MEAL for the day).

    array of:

    string

400
Missing/invalid action, bad timestamp or source, or "me" could not be resolved
422
Punch is not legal given the day's existing punches (e.g. already clocked in, meal already taken, clock-out during a meal)

GET /api/purple-suite/time/clock/status

Get an employee's live clock and meal-break status

Answers "am I clocked in?" and "do I owe a lunch break yet?" from the day's punches and the employee's country meal rule. Use `minutes_until_meal_required` (negative when overdue) and `hours_worked_today` to ground notification copy.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
employee_emailquerystringnoEmployee to report on. Defaults to the effective user; accepts "me".
employeequerystringnoShorthand alias for employee_email.
datequerystringnoWork date to evaluate (UTC). Defaults to today.

Responses

200
Clock status

application/json

ClockStatus

  • employee_email string
  • date string
  • clocked_in boolean
  • clock_in_time string
  • hours_worked_today number
  • on_meal boolean
  • on_break boolean
  • meal_taken_today boolean
  • threshold_hours number
  • minutes_until_meal_required integerNegative when the meal break is already overdue; null when nothing is owed.
  • meal_break_overdue boolean
  • meal_reminder_due booleanTrue inside reminder_lead_minutes of the threshold, or past it.
  • stale_session booleanTrue when the session has been open more than 16 hours — a forgotten clock-out rather than a meal-break case.
  • meal_rule object
    • country string
    • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
    • meal_duration_minutes integer
    • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
    • paid booleanPaid meals keep accruing worked time.
    • description string
  • current_shift object

    Shift

    • id string
    • employee_email string required
    • date string required
    • start_time string
    • end_time string
    • position string
    • department string
    • location string
    • status string
    • createdAt string
    • updatedAt string
  • punches array

    array of:

    Punch

    • id string
    • employee_email string required
    • employee_id string
    • type string required
    • timestamp string required
    • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
    • source string
    • shift_id string
    • notes string
    • createdAt string
    • updatedAt string
400
"me" could not be resolved

POST /api/purple-suite/time/meal-compliance/evaluate

Evaluate meal-break compliance and raise reminders

The meal-break detector. Scans every employee with punches on the given date; for each open session within `reminder_lead_minutes` of — or already past — the country's meal threshold, it upserts one open MEAL_BREAK_DUE exception for that employee/day and fires the `time.meal_break.due` webhook. Sessions open more than 16h are treated as a forgotten clock-out and flagged MISSED_PUNCH instead. Exceptions are idempotent (re-running never stacks duplicates) but the webhook fires on every call, so this endpoint doubles as an on-cue demo trigger. Call it with no body to evaluate the whole instance.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json

  • employee_email stringEvaluate only this employee. Omit to evaluate everyone.
  • date stringWork date to evaluate (UTC). Defaults to today.

Responses

200
Evaluation summary

application/json

MealComplianceResult

  • date string
  • evaluated integerEmployees with punches on the evaluated date.
  • reminders arrayOne entry per employee who owes a meal break; each fired time.meal_break.due.
    One entry per employee who owes a meal break; each fired time.meal_break.due.

    array of:

    • employee_email string
    • clock_in_time string
    • hours_worked number
    • threshold_hours number
    • exception_id string
    • overdue boolean
    • minutes_until_required integer
    • date string
  • missed_punches arraySessions open more than 16h — flagged MISSED_PUNCH instead of a meal reminder.
    Sessions open more than 16h — flagged MISSED_PUNCH instead of a meal reminder.

    array of:

    • employee_email string
    • exception_id string
    • clock_in_time string
    • hours_worked number

GET /api/purple-suite/time/punches

List punches

Raw clock events — the punch-level record behind every hours figure in this app. One row per clock in/out, meal start/end or break start/end, stamped with the work date (`day`) the punch belongs to. Purple HRS has no equivalent: its timesheets are weekly summaries with no punch times.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Punch

    • id string
    • employee_email string required
    • employee_id string
    • type string required
    • timestamp string required
    • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
    • source string
    • shift_id string
    • notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/time/punches

Create punch

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Punch

  • id string
  • employee_email string required
  • employee_id string
  • type string required
  • timestamp string required
  • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
  • source string
  • shift_id string
  • notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Punch

  • id string
  • employee_email string required
  • employee_id string
  • type string required
  • timestamp string required
  • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
  • source string
  • shift_id string
  • notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/time/punches/{id}

Get punch

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Punch

  • id string
  • employee_email string required
  • employee_id string
  • type string required
  • timestamp string required
  • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
  • source string
  • shift_id string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/time/punches/{id}

Delete punch

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/time/punches/{id}

Update punch

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Punch

  • id string
  • employee_email string required
  • employee_id string
  • type string required
  • timestamp string required
  • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
  • source string
  • shift_id string
  • notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Punch

  • id string
  • employee_email string required
  • employee_id string
  • type string required
  • timestamp string required
  • day stringWork date (UTC) the punch belongs to — the field the detector scopes by.
  • source string
  • shift_id string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/time/timecards

List timecards

One employee's timecard for a PAY PERIOD, with totals recomputed from their actual punches (regular / overtime / PTO hours) plus an exception count and approver. This is the authoritative record of hours worked. Do not confuse it with Purple HRS `/timesheets`, which is a WEEKLY hours summary for payroll with no punches behind it — use that one for payroll-facing weekly hours, and this one for what was actually worked.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Timecard

    • id string
    • employee_email string required
    • pay_period_id string required
    • period_start_date string
    • period_end_date string
    • status string
    • totals objectRecomputed from the period's punches whenever a punch is recorded.
      Recomputed from the period's punches whenever a punch is recorded.
      • regular_hours number
      • overtime_hours number
      • pto_hours number
      • total_hours number
    • exception_count integer
    • approver_email string
    • submitted_by string
    • submitted_at string
    • approved_by string
    • approved_at string
    • approver_notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/time/timecards

Create timecard

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/time/timecards/{id}

Get timecard

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/time/timecards/{id}

Delete timecard

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/time/timecards/{id}

Update timecard

Partial update. Status must be one of open, submitted, approved, signed_off; a signed-off timecard cannot be reopened. Prefer /timecards/{id}/submit and /approve for the state machine.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/time/timecards/{id}/submit

Submit a timecard for approval

Employee action: transitions an open timecard to submitted and stamps submitted_by/submitted_at. Emits time.timecard.submitted.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • submitted_by stringDefaults to the timecard's employee_email.

Responses

200
Timecard submitted

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string
404
Timecard not found
422
Timecard is not in 'open' status

POST /api/purple-suite/time/timecards/{id}/approve

Approve a submitted timecard (manager sign-off)

Manager action: transitions a submitted timecard to approved and records approved_by/approver_email. Emits time.timecard.approved.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • approved_by stringDefaults to the effective user, then the timecard's approver_email.
  • approver_notes string

Responses

200
Timecard approved

application/json

Timecard

  • id string
  • employee_email string required
  • pay_period_id string required
  • period_start_date string
  • period_end_date string
  • status string
  • totals objectRecomputed from the period's punches whenever a punch is recorded.
    Recomputed from the period's punches whenever a punch is recorded.
    • regular_hours number
    • overtime_hours number
    • pto_hours number
    • total_hours number
  • exception_count integer
  • approver_email string
  • submitted_by string
  • submitted_at string
  • approved_by string
  • approved_at string
  • approver_notes string
  • createdAt string
  • updatedAt string
404
Timecard not found
422
Timecard is not in 'submitted' status

GET /api/purple-suite/time/shifts

List shifts

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Shift

    • id string
    • employee_email string required
    • date string required
    • start_time string
    • end_time string
    • position string
    • department string
    • location string
    • status string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/time/shifts

Create shift

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Shift

  • id string
  • employee_email string required
  • date string required
  • start_time string
  • end_time string
  • position string
  • department string
  • location string
  • status string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Shift

  • id string
  • employee_email string required
  • date string required
  • start_time string
  • end_time string
  • position string
  • department string
  • location string
  • status string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/time/shifts/{id}

Get shift

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Shift

  • id string
  • employee_email string required
  • date string required
  • start_time string
  • end_time string
  • position string
  • department string
  • location string
  • status string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/time/shifts/{id}

Delete shift

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/time/shifts/{id}

Update shift

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Shift

  • id string
  • employee_email string required
  • date string required
  • start_time string
  • end_time string
  • position string
  • department string
  • location string
  • status string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Shift

  • id string
  • employee_email string required
  • date string required
  • start_time string
  • end_time string
  • position string
  • department string
  • location string
  • status string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/time/exceptions

List exceptions

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    TimeException

    • id string
    • employee_email string required
    • date string
    • type string required
    • severity string
    • status string
    • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
    • detected_at string
    • resolved_by string
    • resolved_at string
    • resolution_note string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/time/exceptions

Create exception

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/time/exceptions/{id}

Get exception

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/time/exceptions/{id}

Delete exception

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/time/exceptions/{id}

Update exception

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/time/exceptions/{id}/resolve

Resolve a time exception

Marks the exception resolved and records resolved_by/resolved_at. Emits time.exception.resolved. Note that recording a meal_start punch resolves meal exceptions automatically — this endpoint is for the other types (missed punches, late-ins) or for manual closure.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • resolved_by stringDefaults to the effective user, then the exception's employee_email.
  • resolution_note string

Responses

200
Exception resolved

application/json

TimeException

  • id string
  • employee_email string required
  • date string
  • type string required
  • severity string
  • status string
  • details objectType-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.
  • detected_at string
  • resolved_by string
  • resolved_at string
  • resolution_note string
  • createdAt string
  • updatedAt string
404
Exception not found
422
Exception is already resolved

GET /api/purple-suite/time/meal-rules

List meal rules

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    MealRule

    • country string
    • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
    • meal_duration_minutes integer
    • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
    • paid booleanPaid meals keep accruing worked time.
    • description string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

GET /api/purple-suite/time/meal-rules/{id}

Get meal rule

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

MealRule

  • country string
  • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
  • meal_duration_minutes integer
  • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
  • paid booleanPaid meals keep accruing worked time.
  • description string
404
Not found

PATCH /api/purple-suite/time/meal-rules/{id}

Update meal rule

Tune a country's meal policy (thresholds must be non-negative numbers). Lowering meal_required_after_hours is the quickest way to make the detector fire during a demo.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

MealRule

  • country string
  • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
  • meal_duration_minutes integer
  • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
  • paid booleanPaid meals keep accruing worked time.
  • description string

Responses

200
Updated

application/json

MealRule

  • country string
  • meal_required_after_hours numberHours worked after which an unpaid meal break is required.
  • meal_duration_minutes integer
  • reminder_lead_minutes integerHow early before the threshold a reminder should fire.
  • paid booleanPaid meals keep accruing worked time.
  • description string
404
Not found

GET /api/purple-suite/time/pay-periods

List pay periods

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    PayPeriod

    • id string
    • start_date string
    • end_date string
    • status string
    • sign_off_due string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

GET /api/purple-suite/time/pay-periods/{id}

Get pay period

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

PayPeriod

  • id string
  • start_date string
  • end_date string
  • status string
  • sign_off_due string
  • createdAt string
  • updatedAt string
404
Not found