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
Endpoints
POST /api/purple-suite/time/clock
Record a punch (clock in/out, meal, break)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
actionstring required — Which punch to record.employee_emailstring — Employee to punch for. Defaults to the effective user; accepts "me".timestampstring — When the punch happened. Defaults to now.sourcestringnotesstringshift_idstring — Defaults to today's scheduled shift.employee_idstring
Responses
201- Punch recorded
application/jsonPunchResultpunchobjectPunchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
statusobjectemployee_emailstringdatestringclocked_inbooleanclock_in_timestringhours_worked_todaynumberon_mealbooleanon_breakbooleanmeal_taken_todaybooleanthreshold_hoursnumberminutes_until_meal_requiredinteger — Negative when the meal break is already overdue; null when nothing is owed.meal_break_overduebooleanmeal_reminder_dueboolean — True inside reminder_lead_minutes of the threshold, or past it.stale_sessionboolean — True when the session has been open more than 16 hours — a forgotten clock-out rather than a meal-break case.meal_ruleobjectcountrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
current_shiftobjectShiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
punchesarrayarray of:
Punchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
resolved_exceptionsarray — Exception 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
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
employee_email | query | string | no | Employee to report on. Defaults to the effective user; accepts "me". |
employee | query | string | no | Shorthand alias for employee_email. |
date | query | string | no | Work date to evaluate (UTC). Defaults to today. |
Responses
200- Clock status
application/jsonClockStatusemployee_emailstringdatestringclocked_inbooleanclock_in_timestringhours_worked_todaynumberon_mealbooleanon_breakbooleanmeal_taken_todaybooleanthreshold_hoursnumberminutes_until_meal_requiredinteger — Negative when the meal break is already overdue; null when nothing is owed.meal_break_overduebooleanmeal_reminder_dueboolean — True inside reminder_lead_minutes of the threshold, or past it.stale_sessionboolean — True when the session has been open more than 16 hours — a forgotten clock-out rather than a meal-break case.meal_ruleobjectcountrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
current_shiftobjectShiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
punchesarrayarray of:
Punchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
400- "me" could not be resolved
POST /api/purple-suite/time/meal-compliance/evaluate
Evaluate meal-break compliance and raise reminders
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json
employee_emailstring — Evaluate only this employee. Omit to evaluate everyone.datestring — Work date to evaluate (UTC). Defaults to today.
Responses
200- Evaluation summary
application/jsonMealComplianceResultdatestringevaluatedinteger — Employees with punches on the evaluated date.remindersarray — One 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_emailstringclock_in_timestringhours_workednumberthreshold_hoursnumberexception_idstringoverduebooleanminutes_until_requiredintegerdatestring
missed_punchesarray — Sessions 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_emailstringexception_idstringclock_in_timestringhours_workednumber
GET /api/purple-suite/time/punches
List punches
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Punchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Punch
idstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonPunchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/time/punches/{id}
Get punch
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonPunchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/time/punches/{id}
Delete punch
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/time/punches/{id}
Update punch
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Punch
idstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonPunchidstringemployee_emailstring requiredemployee_idstringtypestring requiredtimestampstring requireddaystring — Work date (UTC) the punch belongs to — the field the detector scopes by.sourcestringshift_idstringnotesstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/time/timecards
List timecards
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Timecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Timecard
idstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonTimecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/time/timecards/{id}
Get timecard
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonTimecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/time/timecards/{id}
Delete timecard
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/time/timecards/{id}
Update timecard
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Timecard
idstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonTimecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/time/timecards/{id}/submit
Submit a timecard for approval
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
submitted_bystring — Defaults to the timecard's employee_email.
Responses
200- Timecard submitted
application/jsonTimecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
approved_bystring — Defaults to the effective user, then the timecard's approver_email.approver_notesstring
Responses
200- Timecard approved
application/jsonTimecardidstringemployee_emailstring requiredpay_period_idstring requiredperiod_start_datestringperiod_end_datestringstatusstringtotalsobject — Recomputed from the period's punches whenever a punch is recorded.Recomputed from the period's punches whenever a punch is recorded.regular_hoursnumberovertime_hoursnumberpto_hoursnumbertotal_hoursnumber
exception_countintegerapprover_emailstringsubmitted_bystringsubmitted_atstringapproved_bystringapproved_atstringapprover_notesstringcreatedAtstringupdatedAtstring
404- Timecard not found
422- Timecard is not in 'submitted' status
GET /api/purple-suite/time/shifts
List shifts
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Shiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Shift
idstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonShiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/time/shifts/{id}
Get shift
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonShiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/time/shifts/{id}
Delete shift
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/time/shifts/{id}
Update shift
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Shift
idstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonShiftidstringemployee_emailstring requireddatestring requiredstart_timestringend_timestringpositionstringdepartmentstringlocationstringstatusstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/time/exceptions
List exceptions
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
TimeExceptionidstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
TimeException
idstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonTimeExceptionidstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
GET /api/purple-suite/time/exceptions/{id}
Get exception
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonTimeExceptionidstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/time/exceptions/{id}
Delete exception
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/time/exceptions/{id}
Update exception
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
TimeException
idstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonTimeExceptionidstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/time/exceptions/{id}/resolve
Resolve a time exception
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
resolved_bystring — Defaults to the effective user, then the exception's employee_email.resolution_notestring
Responses
200- Exception resolved
application/jsonTimeExceptionidstringemployee_emailstring requireddatestringtypestring requiredseveritystringstatusstringdetailsobject — Type-specific evidence, e.g. hours_worked / threshold_hours / clock_in_time.detected_atstringresolved_bystringresolved_atstringresolution_notestringcreatedAtstringupdatedAtstring
404- Exception not found
422- Exception is already resolved
GET /api/purple-suite/time/meal-rules
List meal rules
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
MealRulecountrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonMealRulecountrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
404- Not found
PATCH /api/purple-suite/time/meal-rules/{id}
Update meal rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
MealRule
countrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
Responses
200- Updated
application/jsonMealRulecountrystringmeal_required_after_hoursnumber — Hours worked after which an unpaid meal break is required.meal_duration_minutesintegerreminder_lead_minutesinteger — How early before the threshold a reminder should fire.paidboolean — Paid meals keep accruing worked time.descriptionstring
404- Not found
GET /api/purple-suite/time/pay-periods
List pay periods
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
PayPeriodidstringstart_datestringend_datestringstatusstringsign_off_duestringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonPayPeriodidstringstart_datestringend_datestringstatusstringsign_off_duestringcreatedAtstringupdatedAtstring
404- Not found