{"consumes":["application/json"],"definitions":{"AbsenceShifts":{"description":"A shift linked to an absence period","example":{"end_datetime":"2024-06-15T16:00:00Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","is_shift_run":false,"is_travel_gap":false,"shift_break":{"duration":30},"start_datetime":"2024-06-15T08:00:00Z"},"properties":{"end_datetime":{"description":"The end datetime of the shift","type":"string"},"id":{"description":"The Sona unique identifier of the shift","type":"string"},"is_shift_run":{"description":"Whether this shift is a shift run parent","type":"boolean"},"is_travel_gap":{"description":"Whether this shift is a travel gap between shifts in a shift run","type":"boolean"},"payroll_run_rows":{"description":"Raw payroll run rows for this shift, present when include_payroll_run_rows=true","items":{"$ref":"#/definitions/PayrollRunRow"},"type":"array"},"shift_break":{"properties":{"duration":{"description":"The duration of the break, in minutes","type":"integer"}},"type":"object","x-nullable":true},"shift_run_id":{"description":"The ID of the parent shift run, if this shift is part of a shift run","type":"string","x-nullable":true},"start_datetime":{"description":"The start datetime of the shift","type":"string"}},"required":["is_travel_gap","is_shift_run","end_datetime","start_datetime","id"],"title":"Absence Shift","type":"object"},"AbsenceSubtype":{"description":"A single absence_subtype","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example absence_subtype"},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the absence_subtype","type":"string"}},"required":["name","id"],"title":"AbsenceSubtype","type":"object"},"AbsenceSubtypes":{"description":"A collection of AbsenceSubtypes","items":{"$ref":"#/definitions/AbsenceSubtype"},"title":"AbsenceSubtypes","type":"array"},"AbsenceType":{"description":"A single absence_type","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","is_holiday":true,"is_sickness":false,"name":"An example absence_type","payroll_type":"holiday"},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"is_holiday":{"description":"If this is a holiday absence or not","type":"boolean"},"is_sickness":{"description":"If this is a sickness absence or not","type":"boolean"},"name":{"description":"The name of the absence_type","type":"string"},"payroll_type":{"description":"The payroll type of the absence_type","type":"string","x-nullable":true}},"required":["is_holiday","name","id"],"title":"AbsenceType","type":"object"},"AbsenceTypes":{"description":"A collection of AbsenceTypes","items":{"$ref":"#/definitions/AbsenceType"},"title":"AbsenceTypes","type":"array"},"AbsenceYear":{"description":"The absence year","example":{"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","denomination":"hours","end_date":"2024-12-31","holiday_calculation_type":"none","id":"550e8400-e29b-41d4-a716-446655440000","start_date":"2024-01-01","user_id":"550e8400-e29b-41d4-a716-446655440000"},"properties":{"accrued":{"description":"Holiday accrued amount","format":"float","type":"number","x-nullable":true},"contract_id":{"description":"The unique Sona identifier of the contract of the absence year, or null for an absence year without a contract","type":"string","x-nullable":true},"denomination":{"description":"Absence year denomination","enum":["hours","days"],"type":"string"},"end_date":{"description":"Absence year end date","format":"date","type":"string"},"holiday_calculation_type":{"description":"Holiday calculation type","enum":["none","days_per_week","days_per_week_flexible","irregular_and_part_year","twelve_point_zero_seven","days_per_week_with_overtime_accrual"],"type":"string"},"id":{"description":"The unique identifier of the absence year entry","type":"string"},"start_date":{"description":"Absence year start date","format":"date","type":"string"},"user_id":{"description":"The unique identifier of the user","type":"string"},"yearly_allowance":{"description":"Holiday allowance","format":"float","type":"number","x-nullable":true}},"title":"The absence year","type":"object"},"Absences":{"description":"A collection of Absences","items":{"$ref":"#/definitions/ShowAbsenceResponse"},"title":"Absences","type":"array"},"AddPermissionRolesPermissionRoleParameters":{"description":"Parameters for adding permission roles to an employment period","example":{"org_unit_ids":["a1b2c3d4-5678-4def-abcd-111111111111","d4e5f6a7-8901-4bcd-efab-333333333333"]},"properties":{"org_unit_ids":{"description":"The unique Sona identifiers of the org units","items":{"type":"string"},"type":"array"}},"title":"Add Permission Role Parameters","type":"object"},"Agencies":{"description":"A collection of agencies","items":{"$ref":"#/definitions/Agency"},"title":"Agencies","type":"array"},"Agency":{"description":"A single agency","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example agency"},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the agency","type":"string"}},"required":["name","id"],"title":"Agency","type":"object"},"AmendedAbsence":{"description":"An amended absence","example":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"df45bc78-c5ee-493c-972d-55d0f5041b1e","ends_at":"2024-06-18T00:00:00Z","id":"4e0a40d6-593e-492a-a57c-7069c9cd5aff","is_open_ended":false,"starts_at":"2024-06-15T00:00:00Z"},"properties":{"absence_subtype_id":{"description":"Absence subtype ID","type":"string","x-nullable":true},"absence_type_id":{"description":"Absence type ID","type":"string"},"ends_at":{"description":"The end date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`). If the absence is open-ended then this is the expected end date.","type":"string"},"id":{"description":"Absence ID","type":"string"},"is_open_ended":{"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"starts_at":{"description":"The start date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"}},"required":["is_open_ended","ends_at","starts_at","absence_type_id","id"],"title":"Amended Absence","type":"object"},"AmendedShift":{"description":"An amended shift","example":{"absence_period_id":"4e0a40d6-593e-492a-a57c-7069c9cd5aff","end_datetime":"2024-06-15T16:00:00Z","id":"cbea106a-ade7-4a9e-8f5c-8fcf0cc5fd9a","org_unit_id":"8de4a59e-7488-4631-b809-c675c39329ba","start_datetime":"2024-06-15T08:00:00Z","user_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb"},"properties":{"absence_period_id":{"description":"Linked absence period ID","type":"string"},"cancelled_at":{"description":"The date time the shifts was cancelled in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"},"end_datetime":{"description":"The end date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"},"id":{"description":"Shift ID","type":"string"},"org_unit_id":{"description":"Organisational unit ID","type":"string"},"start_datetime":{"description":"The start date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"},"user_id":{"description":"User ID","type":"string"}},"required":["end_datetime","start_datetime","user_id","org_unit_id","id"],"title":"Amended Shift","type":"object"},"AmendedShifts":{"description":"A collection of amended shifts","items":{"$ref":"#/definitions/AmendedShift"},"title":"Amended Shifts","type":"array"},"AmendmentAbsenceParameters":{"description":"Parameters for amending an absence period","example":{"ends_at":"2025-01-13T00:00:00Z","is_open_ended":false,"payrolled_shifts_options":{"during_absence_period":{"action":"link","pay_variant":"Sickness"},"outside_absence_period":{"action":"unlink","pay_variant":"Sickness"}},"starts_at":"2025-01-12T00:00:00Z"},"properties":{"create_replacement_shifts":{"default":false,"description":"If open shifts should be created for the shifts contained within an absence period.","type":"boolean"},"ends_at":{"description":"The end date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`). If the absence is open-ended then this is the expected end date.","type":"string"},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"payrolled_shifts_options":{"description":"Actions to take for payrolled shifts that overlap with the amended absence period","properties":{"during_absence_period":{"description":"Action to take for payrolled shifts that will fall inside of the absence period after the amendment","properties":{"action":{"description":"The action to apply to payrolled shifts that happen during the absence period","enum":["cancel","link","unlink"],"type":"string"},"pay_variant":{"description":"The pay variant id to use for the shifts. Required when action is link or unlink.","type":"string"}},"required":["action"],"type":"object"},"outside_absence_period":{"description":"Action to take for payrolled shifts that will fall outside of the absence period after the amendment","properties":{"action":{"description":"The action to apply to payrolled shifts that happen outside the absence period","enum":["cancel","link","unlink"],"type":"string"},"pay_variant":{"description":"The pay variant id to use for the shifts. Required when action is link or unlink.","type":"string"}},"type":"object"}},"type":"object"},"starts_at":{"description":"The start date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"}},"title":"Amendment Absence Period Parameters","type":"object"},"AnnualLeaveRequest":{"description":"Annual leave request","example":{"actual_duration":7.5,"denomination":"hours","duration":7,"end_date":"2022-12-05","external_ids":[{"idx":"ABC","source_type":"example.com"}],"id":"ea72e3cc-b8c6-433a-87e3-d14520e21430","request_type":"holiday","start_date":"2022-12-05","state":"taken","user_id":"9925c220-a290-464a-9f7e-16f60cd70052"},"properties":{"actual_duration":{"description":"The actual duration of the annual leave","type":"number","x-nullable":true},"contract_id":{"description":"The unique Sona identifier of the contract of the request's absence year, or null for an absence year without a contract","type":"string","x-nullable":true},"denomination":{"description":"The unit in which the duration is given","enum":["days","hours"],"type":"string"},"duration":{"description":"The total duration of the requested annual leave","type":"number"},"end_date":{"description":"The end date of the requested annual leave","type":"string"},"external_ids":{"description":"A list of external IDs of the annual leave request.","items":{"properties":{"idx":{"description":"The external ID","type":"string"},"source_type":{"description":"The source type of the external ID","type":"string"}},"title":"External ID","type":"object"},"type":"array"},"id":{"description":"The identifier of the annual leave request","type":"string"},"request_type":{"description":"The kind of leave requested","enum":["holiday","maternity"],"type":"string"},"start_date":{"description":"The start date of the requested annual leave","type":"string"},"state":{"description":"The state the request is in","enum":["pending","confirmed","taken","rejected","cancelled","in_progress"],"type":"string"},"user_id":{"description":"The ID of the user the request is for","type":"string"}},"title":"Annual leave request","type":"object"},"AssignToUserParameters":{"description":"Parameters for assigning an attribute or a skill to user","example":{"end_date":"2025-06-30","start_date":"2025-01-01","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"end_date":{"description":"The optional date on which the skill ends. Can't be used for attributes.","type":"string"},"start_date":{"description":"The optional date on which the skill starts. Can't be used for attributes.","type":"string"},"user_id":{"description":"The identifier of the user the skill or attribute should be assigned to","type":"string"}},"required":["user_id"],"title":"Assign skill or attribute to user parameters","type":"object"},"BatchQueryParams":{"description":"Parameters for a batch org attribute query","example":{"attribute_slugs":["opening_time","closing_time"],"end_date":"2026-02-03","org_unit_ids":["e27ad021-af0e-4ccf-95e8-1c7c84656c93","f38be132-bf1f-5dd0-a6f9-2d8d95767da4"],"start_date":"2026-02-01"},"properties":{"attribute_slugs":{"description":"The slugs of the org attributes to query","items":{"type":"string"},"type":"array"},"end_date":{"description":"The end of the date range, inclusive (ISO 8601). Defaults to start_date","format":"date","type":"string"},"org_unit_ids":{"description":"Sona unique identifiers of the org units to query","items":{"format":"uuid","type":"string"},"type":"array"},"start_date":{"description":"The start of the date range (ISO 8601). Defaults to today","format":"date","type":"string"}},"required":["attribute_slugs","org_unit_ids"],"title":"BatchQueryParams","type":"object"},"BatchQueryResponse":{"description":"A map of org unit IDs to their attribute values. Each org unit entry contains a map of attribute slugs to date/value pairs. An org unit with no data is null.","example":{"data":{"e27ad021-af0e-4ccf-95e8-1c7c84656c93":{"closing_time":{"2026-02-01":"22:00:00","2026-02-02":"22:00:00","2026-02-03":"22:00:00"},"opening_time":{"2026-02-01":"08:00:00","2026-02-02":"08:00:00","2026-02-03":"10:00:00"}}}},"properties":{"data":{"description":"Org unit attribute values keyed by org unit ID","type":"object"}},"title":"BatchQueryResponse","type":"object"},"CarePackageCreateParams":{"example":{"banking_enabled":false,"hours":[{"assignment_count":1,"demand_period_ids":["7a1f0c9e-2b3d-4c5e-8f60-1a2b3c4d5e6f"],"hours":84,"org_unit_idx":"SVC001","staff_count":1,"type_of_work_idx":"PERSONAL_CARE"}],"idx":"pkg_a_2026","name":"Alice 2026","shared_care_during_specified":true,"start_date":"2026-01-01"},"properties":{"banking_enabled":{"description":"Defaults to false","type":"boolean"},"end_date":{"description":"ISO 8601 date. Omit or null for ongoing.","type":"string","x-nullable":true},"hours":{"description":"At least one, up to 100","items":{"$ref":"#/definitions/HourLineInput"},"type":"array"},"idx":{"description":"Your own id for the package. Unique in your organisation.","type":"string"},"location":{"description":"Optional. Updates the person's residency address.","properties":{"address_line_1":{"description":"Address line 1","type":"string"},"address_line_2":{"description":"Address line 2","type":"string"},"address_line_3":{"description":"Address line 3","type":"string"},"address_line_4":{"description":"Address line 4","type":"string"},"city":{"description":"City","type":"string"},"country":{"description":"Country name","type":"string"},"name":{"description":"Location name","type":"string"},"postcode":{"description":"Postal code","type":"string"}},"type":"object"},"name":{"description":"Package name","type":"string","x-nullable":true},"notes":{"description":"Notes","type":"string","x-nullable":true},"shared_care_during_specified":{"description":"Defaults to true","type":"boolean"},"start_date":{"description":"ISO 8601 date, after the current package's start","type":"string"}},"required":["hours","start_date","idx"],"title":"Care package create params","type":"object"},"CarePackageData":{"example":{"banking_enabled":false,"hours":[{"assignment_count":1,"demand_periods":[{"id":"7a1f0c9e-2b3d-4c5e-8f60-1a2b3c4d5e6f","name":"Day"}],"hours":84.0,"id":"8f933d28-b46d-489d-add9-8d22396f239e","org_unit_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","org_unit_idx":"SVC001","staff_count":1,"status":"pending","type_of_work_id":"98e14a4e-0fb7-42cb-9be9-6a4c983385b4","type_of_work_idx":"PERSONAL_CARE"}],"id":"2b787254-90c4-4191-aa0f-102ce5bed757","idx":"pkg_a_2026","name":"Alice 2026","pws_id":"c783afae-2cad-4b8f-9151-512ee558ba6b","pws_idx":"PWS001","shared_care_during_specified":true,"start_date":"2026-01-01","status":"pending","updated_at_utc":"2026-10-12T09:00:00Z"},"properties":{"banking_enabled":{"description":"Banking enabled","type":"boolean"},"end_date":{"description":"ISO 8601 date, the last day in force. Null if it has no end yet.","type":"string","x-nullable":true},"hours":{"description":"The commissioned hour lines, in the order they were given (a fixed but possibly different order on packages created before 23 October 2026)","items":{"$ref":"#/definitions/HourLineData"},"type":"array"},"id":{"description":"Care package id","type":"string"},"idx":{"description":"Package code. Null for packages made in Sona without one.","type":"string","x-nullable":true},"name":{"description":"Name","type":"string","x-nullable":true},"notes":{"description":"Notes","type":"string","x-nullable":true},"pws_id":{"description":"The person","type":"string"},"pws_idx":{"description":"The person's `upload_id`","type":"string","x-nullable":true},"shared_care_during_specified":{"description":"Shared care during specified hours","type":"boolean"},"start_date":{"description":"ISO 8601 date","type":"string"},"status":{"description":"`pending`, `pre_approved`, `approved`, `rejected` or `partially_approved`","type":"string","x-nullable":true},"superseded_by_id":{"description":"The package that replaced it","type":"string","x-nullable":true},"updated_at_utc":{"description":"When the package or an hour line's approval status last changed, UTC ISO 8601","type":"string"}},"title":"Care package data","type":"object"},"CarePackageDataList":{"items":{"$ref":"#/definitions/CarePackageData"},"title":"Care packages","type":"array"},"CompleteForecastRunParameters":{"description":"Parameters for completing a forecast run","example":{"ended_at":"2026-04-27T09:14:32Z","train_wape":7.66},"properties":{"ended_at":{"description":"ISO8601 timestamp when the forecast finished","type":"string"},"train_wape":{"description":"Train WAPE percentage between 0 and 100","type":"number"}},"required":["ended_at"],"title":"CompleteForecastRunParameters","type":"object"},"ContractType":{"description":"A single contract type","example":{"booking_granularity":"half_hour","denomination":"hours","employment_type":"full_time","holiday_allowance_rounding":"full_hour","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example contract type","pay_period_start":"monday","pay_period_type":"weekly","payroll_rule":"hourly"},"properties":{"booking_granularity":{"description":"Granularity for annual leave booking","enum":["full_day","half_day","full_hour","half_hour","quarter_hour","minute"],"type":"string","x-nullable":true},"denomination":{"description":"The denomination for holiday allowances","enum":["hours","days"],"type":"string","x-nullable":true},"employment_basis":{"description":"Free-text employment basis label, or null when not set","type":"string","x-nullable":true},"employment_type":{"description":"The employment type classification for this contract. This field is a property of the contract type, not a top-level field on the employment period.","enum":["full_time","part_time","flexible"],"type":"string"},"holiday_allowance_rounding":{"description":"Rounding method for calculating holiday allowance","enum":["full_day","half_day","full_hour","half_hour","quarter_hour","minute"],"type":"string","x-nullable":true},"id":{"description":"Sona unique identifier","type":"string"},"include_absence_cost":{"default":false,"description":"Whether paid absence days contribute to wage cost calculations","type":"boolean"},"include_absence_hours":{"default":true,"description":"Whether absence hours count towards the wage cost hours metric. Only has an effect when include_absence_cost is true","type":"boolean"},"name":{"description":"The name of the contract type","type":"string"},"only_worked_days":{"default":false,"description":"Whether salary distributions should consider only days worked","type":"boolean"},"pay_period_start":{"description":"The day of the week the pay period starts","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"},"pay_period_type":{"description":"The type of pay period","enum":["weekly","monthly"],"type":"string"},"payroll_rule":{"description":"How the pay is paid","enum":["hourly","fixed_salary"],"type":"string"},"worked_days_in_year":{"description":"Number of worked days in the year to distribute salary across when only worked days are enabled (supports decimals)","maximum":365.0,"minimum":1.0,"type":"number","x-nullable":true},"worked_weeks_in_year":{"description":"Number of worked weeks in the year to distribute salary across for weekly salary distribution (supports decimals)","maximum":52.0,"minimum":1.0,"type":"number","x-nullable":true}},"required":["payroll_rule","pay_period_start","pay_period_type","name","id"],"title":"ContractType","type":"object"},"ContractTypeParams":{"description":"A contract type in an employment spec request. Only `id` is read: it must be an existing contract type from `GET /api/v1/contract_types`. Other fields are ignored.","example":{"booking_granularity":"half_hour","denomination":"hours","employment_type":"full_time","holiday_allowance_rounding":"full_hour","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example contract type","pay_period_start":"monday","pay_period_type":"weekly","payroll_rule":"hourly"},"properties":{"booking_granularity":{"description":"Granularity for annual leave booking","enum":["full_day","half_day","full_hour","half_hour","quarter_hour","minute"],"type":"string"},"denomination":{"description":"The denomination for holiday allowances","enum":["hours","days"],"type":"string"},"employment_type":{"description":"The employment type classification for this contract. This field is a property of the contract type, not a top-level field on the employment period.","enum":["full_time","part_time","flexible"],"type":"string"},"holiday_allowance_rounding":{"description":"Rounding method for calculating holiday allowance","enum":["full_day","half_day","full_hour","half_hour","quarter_hour","minute"],"type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"include_absence_cost":{"default":false,"description":"Whether paid absence days contribute to wage cost calculations","type":"boolean"},"name":{"description":"The name of the contract type","type":"string"},"only_worked_days":{"default":false,"description":"Whether salary distributions should consider only days worked","type":"boolean"},"pay_period_start":{"description":"The day of the week the pay period starts","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"},"pay_period_type":{"description":"The type of pay period","enum":["weekly","monthly"],"type":"string"},"payroll_rule":{"description":"How the pay is paid","enum":["hourly","fixed_salary"],"type":"string"},"worked_days_in_year":{"description":"Number of worked days in the year to distribute salary across when only worked days are enabled (supports decimals)","maximum":365.0,"minimum":1.0,"type":"number"},"worked_weeks_in_year":{"description":"Number of worked weeks in the year to distribute salary across for weekly salary distribution (supports decimals)","maximum":52.0,"minimum":1.0,"type":"number"}},"required":["id"],"title":"ContractType params","type":"object"},"ContractTypes":{"description":"A collection of ContractTypes","items":{"$ref":"#/definitions/ContractType"},"title":"ContractTypes","type":"array"},"ContractedHour":{"description":"Contracted Hours for this employment spec","example":{"colour_coding":false,"days":5,"finish_date":"2025-03-01","hours":37.5,"id":"c5e3a9d4-6f7b-4012-d34e-5a6b78901cde","idx":"CH-EXT-001","monthly_period_starts_at":1,"period":"weekly","start_date":"2024-03-01","weekly_period_starts_at":"monday"},"properties":{"colour_coding":{"description":"Whether to specially highlight contracted hours on roster","type":"boolean"},"days":{"description":"Total number of contracted days in a period, or null when not set","type":"number","x-nullable":true},"employment_period_id":{"description":"Sona unique identifier of the employment period these hours belong to. Only returned by the `/api/v1/contracted_hours` endpoints.","type":"string","x-nullable":true},"employment_period_idx":{"description":"External identifier of the employment period these hours belong to. Only returned by the `/api/v1/contracted_hours` endpoints.","type":"string","x-nullable":true},"finish_date":{"description":"The date from when these contracted hours are no longer applicable, or null when open-ended","format":"date","type":"string","x-nullable":true},"hours":{"description":"Total number of contracted hours in a period","type":"number"},"id":{"description":"The unique Sona identifier of the contracted hours record","type":"string"},"idx":{"description":"An external identifier of the contracted hours.","type":"string","x-nullable":true},"monthly_period_starts_at":{"description":"The day of the month a monthly period starts at","type":"integer"},"org_unit_id":{"description":"The Sona unique identifier of the org unit these hours apply to","type":"string","x-nullable":true},"org_units":{"description":"The org units these hours apply to. Only returned by the `/api/v1/contracted_hours` endpoints.","items":{"properties":{"id":{"description":"Sona unique identifier of the org unit","type":"string"}},"type":"object"},"type":"array"},"period":{"description":"The type of period","enum":["weekly","monthly","rolling_sequence","calendar_month_daily"],"type":"string"},"rolling_period_sequence_length_days":{"description":"The length of rolling sequence in days","type":"integer","x-nullable":true},"rolling_period_sequence_start":{"description":"When the rolling sequence starts","format":"date","type":"string","x-nullable":true},"start_date":{"description":"The date from when these contracted hours are applicable","format":"date","type":"string"},"type_of_work_id":{"description":"The Sona unique identifier of the type of work these hours apply to","type":"string","x-nullable":true},"types_of_work":{"description":"The types of work these hours apply to. Only returned by the `/api/v1/contracted_hours` endpoints.","items":{"properties":{"id":{"description":"Sona unique identifier of the type of work","type":"string"}},"type":"object"},"type":"array"},"weekly_period_starts_at":{"description":"The day of the week a weekly period starts at","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"required":["monthly_period_starts_at","weekly_period_starts_at","hours","days","finish_date","start_date","id"],"title":"Contracted Hour","type":"object"},"ContractedHourParams":{"description":"params to create Contracted Hours for this employment spec","example":{"days":5,"finish_date":"2025-03-01","hours":37.5,"id":"c5e3a9d4-6f7b-4012-d34e-5a6b78901cde","idx":"CH-EXT-001","monthly_period_starts_at":1,"period":"weekly","start_date":"2024-03-01","weekly_period_starts_at":"monday"},"properties":{"days":{"description":"Total number of contracted days in a period","type":"number"},"finish_date":{"description":"The date from when these contracted hours are no longer applicable","format":"date","type":"string"},"hours":{"description":"Total number of contracted hours in a period","type":"number"},"id":{"description":"The unique Sona identifier of the contracted hours record. Not used on create.","type":"string"},"idx":{"description":"An external identifier of the contracted hours.","type":"string"},"monthly_period_starts_at":{"description":"The day of the month a monthly period starts at","type":"integer"},"org_unit_id":{"description":"The Sona unique identifier of the org unit these hours apply to","type":"string"},"period":{"description":"The type of period e.g. weekly / monthly","enum":["weekly","monthly","rolling_sequence","calendar_month_daily"],"type":"string"},"rolling_period_sequence_length_days":{"description":"The length of rolling sequence in days","type":"integer"},"rolling_period_sequence_start":{"description":"When the rolling sequence starts","format":"date","type":"string"},"start_date":{"description":"The date from when these contracted hours are applicable","format":"date","type":"string"},"type_of_work_id":{"description":"The Sona unique identifier of the type of work these hours apply to","type":"string"},"weekly_period_starts_at":{"description":"The day of the week a weekly period starts at","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"required":["period","hours","start_date"],"title":"Contracted Hour Params","type":"object"},"ContractedHours":{"description":"A collection of ContractedHours","items":{"$ref":"#/definitions/ContractedHour"},"title":"ContractedHours","type":"array"},"ContractedHoursCreateParams":{"description":"Contracted hours","example":{"colour_coding":true,"days":5,"employment_period_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","employment_period_idx":"159600fb-3fa9-4687-bbc9-196375de6ee3","finish_date":"2024-05-05","hours":40.0,"idx":"test_idx","monthly_period_starts_at":5,"org_units":[{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},{"id":"2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"}],"period":"weekly","rolling_period_sequence_length_days":20,"rolling_period_sequence_start":"2024-03-01","start_date":"2024-01-01","types_of_work":[{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}],"weekly_period_starts_at":"tuesday"},"properties":{"colour_coding":{"default":false,"description":"Whether to specially highlight contracted hours on roster","type":"boolean"},"days":{"description":"Amount of days the contracted hours were active","type":"integer"},"employment_period_id":{"description":"The unique Sona identifier of the employment period. At least one of employment_period_id or employment_period_idx is required.","type":"string"},"employment_period_idx":{"description":"External identifier of the employment_period. At least one of employment_period_id or employment_period_idx is required.","type":"string"},"finish_date":{"description":"The date from when these contracted hours are no longer applicable. Expecting YYYY-MM-DD format e.g. \"1990-01-30\"","format":"date","type":"string"},"hours":{"description":"Amount of hours the employee has worked during contracted hours. Bounded by the length of the period — see the endpoint description","type":"number"},"idx":{"description":"An external identifier of the contracted hours.","type":"string"},"monthly_period_starts_at":{"default":1,"description":"Which day of the month the contracted hours start, e.g. on the 15th or on the 1st","type":"integer"},"org_unit_id":{"description":"(Legacy) The unique Sona identifier of a single org unit. Use org_units instead to assign one or more locations.","type":"string"},"org_units":{"description":"Array of org units (locations) to assign. Supports multiple locations. Each entry must be an object with an id field. Takes precedence over org_unit_id when provided.","items":{"$ref":"#/definitions/OrgUnitRef"},"type":"array"},"period":{"description":"Whether the period is monthly or weekly","enum":["weekly","monthly","rolling_sequence","calendar_month_daily"],"type":"string"},"rolling_period_sequence_length_days":{"description":"The length of rolling sequence in days","type":"integer"},"rolling_period_sequence_start":{"description":"When the rolling sequence starts","format":"date","type":"string"},"start_date":{"description":"The date from when these contracted hours are applicable. Expecting YYYY-MM-DD format e.g. \"1990-01-30\"","format":"date","type":"string"},"type_of_work_id":{"description":"(Legacy) The unique Sona identifier of a single type of work. Use types_of_work instead to assign one or more TOWs.","type":"string"},"types_of_work":{"description":"Array of types of work to assign. Supports multiple TOWs. Each entry must be an object with an id field. Takes precedence over type_of_work_id when provided.","items":{"$ref":"#/definitions/TypeOfWorkRef"},"type":"array"},"weekly_period_starts_at":{"description":"Which day of the week the contracted hours start","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"required":["period","hours","start_date"],"title":"Contracted hours params","type":"object"},"CreateAbsenceParameters":{"description":"Parameters for creating an absence","example":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","create_replacement_shifts":false,"ends_at":"2019-04-20T15:47:58+00:00","idx":"some-external-id","is_open_ended":false,"sick_note_provided":false,"starts_at":"2019-04-20T07:47:58+00:00","user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"properties":{"absence_subtype_id":{"description":"Sona unique identifier of the absence_subtype for this absence","type":"string"},"absence_type_id":{"description":"Sona unique identifier of the absence_type for this absence","type":"string"},"create_replacement_shifts":{"default":false,"description":"If open shifts should be created for the shifts contained within an absence period.","type":"boolean"},"ends_at":{"description":"The end date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`). If the absence is open-ended then this is the expected end date.","type":"string"},"idx":{"description":"An external ID for this absence period","type":"string"},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"sick_note_provided":{"default":false,"description":"Whether a sick note has been provided. Optional; defaults to false when left out. Must be true or false; the strings \"true\", \"false\", \"1\" and \"0\" are also accepted, null is not. true is only allowed when the absence type is an SSP absence type. The SSP sick note policy reads it: when an absence is long enough to need a note, false limits SSP to the days that do not need one.","type":"boolean"},"starts_at":{"description":"The start date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_id":{"description":"Sona unique identifier of the user this absence will be assigned to","type":"string"}},"required":["absence_type_id","ends_at","starts_at","user_id"],"title":"Absence Parameters","type":"object"},"CreateAbsenceResponse":{"description":"The absence period that was just created","example":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","days":[{"date":"2019-04-20","status":"not_sick"}],"ends_at":"2019-04-20T15:47:58Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","idx":"some-external-id","is_open_ended":false,"linked_shifts":[{"end_datetime":"2019-04-20T09:47:58Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","is_shift_run":false,"is_travel_gap":false,"shift_break":{"duration":20},"start_datetime":"2019-04-20T08:47:58Z"}],"sick_note_provided":false,"starts_at":"2019-04-20T07:47:58Z","user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"properties":{"absence_subtype_id":{"description":"Sona unique identifier of the absence_subtype for this absence","type":"string","x-nullable":true},"absence_type_id":{"description":"Sona unique identifier of the absence_type for this absence","type":"string"},"days":{"description":"List of days with sick/not sick status","items":{"properties":{"date":{"description":"The date","type":"string"},"status":{"description":"Date status","enum":["sick","not_sick"],"type":"string"}},"type":"object"},"type":"array"},"ends_at":{"description":"UTC Datetime when this absence period ends in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"idx":{"description":"An external ID for this absence period","type":"string","x-nullable":true},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"linked_shifts":{"$ref":"#/definitions/LinkedShifts","description":"A collection of shift ids, start and end datetime that are contained within an absence period"},"sick_note_provided":{"description":"Whether a sick note has been provided for this absence. The SSP sick note policy reads it: when an absence is long enough to need a note, false limits SSP to the days that do not need one. null when it was never recorded.","type":"boolean","x-nullable":true},"starts_at":{"description":"UTC Datetime when this absence period starts in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_id":{"description":"Sona unique identifier of the user for this absence","type":"string"}},"required":["linked_shifts","user_id","absence_type_id","ends_at","starts_at","id"],"title":"Create absence","type":"object"},"CreateAbsenceYearParameters":{"description":"The absence year parameters","example":{"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","start_date":"2024-07-23","user_id":"550e8400-e29b-41d4-a716-446655440000"},"properties":{"contract_id":{"description":"The Sona unique identifier of the contract the request is about, from `contract_id` on `GET /api/v1/employment_periods`. Only used by organisations with overlapping employment periods enabled. Required when the user holds employment periods on several contracts covering the requested date; without it the only contract covering that date is used. A contract whose employment periods have ended still reads its own absence year covering the date.","type":"string"},"start_date":{"description":"The date from when the absence year starts","format":"date","type":"string"},"user_id":{"description":"The Sona unique identifier of the user","type":"string"}},"required":["start_date","user_id"],"title":"The absence year parameters","type":"object"},"CreateAttendanceParameters":{"description":"Parameters to create a training course attendance","example":{"end_date":"2023-01-31","expiry_date":"2023-12-31","idx":"external-idx-123","start_date":"2023-01-01","training_course_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","user_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93"},"properties":{"end_date":{"description":"The end date of the training course attendance","type":"string"},"expiry_date":{"description":"The expiry date of the training course attendance","type":"string"},"idx":{"description":"An external unique identifier for the training course attendance","type":"string"},"start_date":{"description":"The start date of the training course attendance","type":"string"},"training_course_id":{"description":"The Sona unique identifier of the training course. Required if training_course_idx is not provided.","type":"string"},"training_course_idx":{"description":"The external identifier of the training course. Required if training_course_id is not provided.","type":"string"},"user_id":{"description":"The unique identifier of the user. Required if user_idx is not provided.","type":"string"},"user_idx":{"description":"The external identifier of the user. Required if user_id is not provided.","type":"string"}},"required":["end_date","start_date"],"title":"Course Attendance Parameters","type":"object"},"CreateCourseParameters":{"description":"Parameters to create a course","example":{"description":"In-depth guide to safety in the workplace","frequency":"annually","idx":"safety-training-101","mandatory":"true","name":"Safety Training","provider":"Safety Solutions Inc.","type":"online"},"properties":{"description":{"description":"The course description","type":"string"},"frequency":{"description":"Course frequency","enum":["once","weekly","bi_weekly","monthly","quarterly","bi_annually","annually","every_2_years","every_3_years","every_4_years","every_5_years"],"type":"string"},"idx":{"description":"External unique identifier for the course","type":"string"},"mandatory":{"description":"Whether the course is mandatory. Must be 'true' or 'false'.","type":"string"},"name":{"description":"The name of the course","type":"string"},"provider":{"description":"The course provider","type":"string"},"type":{"description":"Course type","enum":["online","classroom","workshop","elearning","on_site","other"],"type":"string"}},"required":["mandatory","name"],"title":"Course Parameters","type":"object"},"CreateExternalMappingParameters":{"description":"Parameters to create an external mapping","example":{"idx":"external_id_of_variable_type","source_type":"general","user_id":"bb000313-0497-443c-b05d-7c68a4f9e812"},"properties":{"absence_type_id":{"description":"Sona unique identifier of of an absence type","type":"string"},"contract_type_id":{"description":"Sona unique identifier of a contract type. Accepted only for `dayforce_pay_policy` mappings; ignored for other source types","type":"string"},"idx":{"description":"An identifier for the external mapping","type":"string"},"job_id":{"description":"Sona unique identifier of a job","type":"string"},"level_id":{"description":"Sona unique identifier of a level","type":"string"},"org_unit_id":{"description":"Sona unique identifier of an org unit","type":"string"},"pay_group_id":{"description":"Sona unique identifier of a pay group","type":"string"},"permission_role_id":{"description":"Sona unique identifier of a permission role","type":"string"},"skill_attribute_id":{"description":"Sona unique identifier of a skill attribute","type":"string"},"source_type":{"description":"Type of source the external mapping relates to. Defaults to `general`","enum":["general","role_mapping_peoplexd","absence_peoplexd","pay_group_peoplexd","store_mapping_harri","oracle_hcm_user","oracle_hcm_org_unit","oracle_hcm_job","oracle_hcm_type_of_work","oracle_hcm_absence_type","oracle_hcm_pay_group","paycom_user","paycom_location","paycom_job","paycom_labor_job","paycom_level","paycom_permission_role","paycom_rate","paycom_punch","paycom_position","oracle_simphony_employee","oracle_simphony_location","oracle_simphony_job_code","oracle_simphony_timecard","assignment_idx","budget_allocation_idx","oracle_hcm_job_code","oracle_hcm_position_name","tao_oracle_hcm_location_department","sap_zalaris_employee","sap_zalaris_job","dayforce_employee","dayforce_location","dayforce_job","dayforce_job_type_of_work","dayforce_suspension_status","dayforce_pay_group","dayforce_pay_policy","dayforce_termination_reason"],"type":"string"},"termination_reason_id":{"description":"Sona unique identifier of a termination reason. Accepted only for `dayforce_termination_reason` mappings; ignored for other source types","type":"string"},"user_id":{"description":"Sona unique identifier of the user for this external mapping","type":"string"}},"required":["idx"],"title":"External mapping parameters","type":"object"},"CreateParams":{"description":"Parameters for creating a custom form entry.\n\n**Fixed fields (always required):**\n- `custom_form_id` — the ID of the custom form to create an entry for.\n- `user_id` — the ID of the user the entry belongs to.\n\n**Conditional field:**\n- `employee_document_type` — **required only when the custom form's `submission_type` is `multiple`**. This selects which document type (and therefore which fieldset) the entry is for. Valid values come from `schema.fieldsets[].variation.value` on the custom form definition (`GET /api/v1/custom_forms/{id}`). For example, a form might have document types like `\"CV\"`, `\"Passport\"`, or `\"Right to Work\"`.\n\n**Dynamic fields (vary per form):**\nAll remaining fields are determined by the custom form's field configuration. Inspect the matching fieldset's `fields[].name` values in the custom form's `schema` to discover the keys you need to include. These will vary between forms and between document types within the same form.\n\n**Important:** field names must exactly match the `name` values from the custom form's schema. The file content must be the value of that field name in the request body — do not send the file under a generic key like `file` or `document`. Any parameter that does not match a known field name is silently ignored, and the corresponding field will be stored as `null` in the response. Always check the `data` object in the response to confirm your fields were received.\n\n**Example 1 — `single` submission type (file upload):**\nThis example is for a form with a single file upload field named `document_1` (discovered via `GET /api/v1/custom_forms/{id}`). The field name `document_1` comes from the form schema — your form may use a different name.\n\n```json\n{\n  \"custom_form_id\": \"655df290-db8e-4c15-98ab-f34a2515cc7d\",\n  \"user_id\": \"74a1af00-d181-4c95-bb6a-aab800185595\",\n  \"document_1\": {\n    \"base64\": \"JVBERi0xLjQKJcTl8uXr... (base64-encoded file content)\",\n    \"filename\": \"onboarding_doc.pdf\",\n    \"content_type\": \"application/pdf\"\n  }\n}\n```\n\n**Example 2 — `multiple` submission type (with document type):**\nThis example is for an Employee Documents form where `employee_document_type` selects the \"CV\" fieldset. The field names `employee_documents_cv_description` and `employee_documents_cv_upload_file` come from that fieldset's field definitions.\n\n```json\n{\n  \"custom_form_id\": \"159600fb-3fa9-4687-bbc9-196375de6ee3\",\n  \"user_id\": \"74a1af00-d181-4c95-bb6a-aab800185595\",\n  \"employee_document_type\": \"CV\",\n  \"employee_documents_cv_description\": \"CV Description\",\n  \"employee_documents_cv_upload_file\": {\n    \"base64\": \"JVBERi0xLjQKJcTl8uXr... (base64-encoded file content)\",\n    \"filename\": \"cv.pdf\",\n    \"content_type\": \"application/pdf\"\n  }\n}\n```\n\nThe structured example below shows only the fixed fields; add the form's own field names alongside them as in the examples above.\n","example":{"custom_form_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","employee_document_type":"CV","user_id":"74a1af00-d181-4c95-bb6a-aab800185595"},"properties":{"custom_form_id":{"description":"The unique Sona identifier of the custom form","type":"string"},"employee_document_type":{"description":"The document type to create an entry for. Required when the custom form has `submission_type: multiple`. Valid values are found in the custom form's `schema.fieldsets[].variation.value`. Not used for `single` submission type forms.","type":"string"},"user_id":{"description":"The unique Sona identifier of a user","type":"string"}},"required":["user_id","custom_form_id"],"title":"Custom form entry params","type":"object"},"CreateUserParameters":{"description":"Parameters for creating or updating a user. Some fields are conditionally required depending on your organisation's configuration. For example, `contact_email` is required on creation when the `make_fields_mandatory` setting is enabled.","example":{"types_of_work":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"sex":"male","pay_group_ids":["2de697e7-29b6-498b-b324-00aa5de7c526"],"contact_email":"jamie_watson_smith@example.com","insurance_number":"NI8682","home_address":{"address_line_1":"24 Seddon Road","address_line_2":"Manchester","address_line_3":"","address_line_4":"","postcode":"MX1 2BR"},"base_roles":[{"name":"manager"}],"read_only_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"home_tier_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"},{"idx":"external_id"}],"next_of_kin":[{"first_name":"Sherlock","last_name":"Holmes"},{"first_name":"Mary","last_name":"Morstan","relationship":"spouse"}],"work_phone_number":"07496756541","work_email":"jamie_watson_smith@example.com","exclude_from_payroll":false,"identifies_as":"non_binary","receives_content_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"external_id":"123700","auth_strategies":[{"key":"user@example.org","type":"email"}],"date_of_birth":"01/02/1978","marital_status":"single","administrative_org_unit":{"id":"fefc811b-f379-4472-9f28-31cb41add989","idx":"some external id"},"middle_name":"Watson","last_name":"Smith","bank_account":{"account_name":"Personal","account_number":"12345678","bank_name":"Bank","country_code":"GB","sort_code":"12-34-56"},"manager_org_units":[{"id":"fefc811b-f379-4472-9f28-31cb41add989"},{"id":"fefc811b-f379-4472-9f28-31fb42add989"}],"legal_first_name":"James","title":"dr","contact_phone_number":"07496756541","first_name":"James","appears_on_roster_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"},{"idx":"external_id"}],"blocked_for_org_units":[{"id":"4ba2cb25-2d7e-4658-95a6-1b6316a8cdd9"},{"idx":"external_id"}],"assignable_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"},{"idx":"external_id"}],"preferred_name":"Jamie","contact_mobile_number":"07901234440"},"properties":{"types_of_work":{"description":"Types of work the user is qualified to do, used for shift matching","items":{"properties":{"id":{"description":"The Sona unique identifier of the type of work","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"sex":{"description":"The sex of the user","enum":["female","male"],"type":"string"},"pay_group_ids":{"description":"List containing a single pay group ID to assign the user to. Omit or pass an empty list to leave the user unassigned. Only one pay group per user is supported.","items":{"type":"string"},"type":"array"},"contact_email":{"description":"The contact email of the user. Optional by default. Required on creation when the `make_fields_mandatory` setting is enabled for your organisation.","type":"string","x-nullable":true},"insurance_number":{"description":"The UK National Insurance number of the user","type":"string"},"home_address":{"description":"The home address of the user","properties":{"address_line_1":{"description":"First line the user's address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line the user's address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line the user's address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line the user's address","type":"string","x-nullable":true},"city":{"description":"The city the user's address","type":"string","x-nullable":true},"country":{"description":"The country of the user's address","type":"string","x-nullable":true},"postcode":{"description":"The postcode of the user's address","type":"string","x-nullable":true}},"type":"object","x-nullable":true},"base_roles":{"description":"The underlying roles the user has in the system (independent of org unit specific roles). If omitted and `manager_org_units` is provided, `manager` is assigned automatically so the user can log in to the manager desktop. If omitted and only `read_only_org_units` is provided, `read_only_manager` is assigned. Pass `base_roles` explicitly to override this default (including `[]` for a user with no manager login access).","items":{"properties":{"name":{"description":"The name of the role","enum":["manager","read_only_manager","hr_manager","content_manager","hr_and_content_manager","payroll_manager","organisation_admin","organisation_user_admin","payroll_reports_admin","management_dashboard_viewer","roster_approvals"],"type":"string"}},"type":"object"},"type":"array"},"read_only_org_units":{"description":"Org units the user has read only access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"home_tier_org_units":{"description":"Org units for which the user appears in the primary tier (e.g. see's available shifts first). To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"next_of_kin":{"description":"User's next of kin","items":{"properties":{"first_name":{"description":"First Name of the User's kin","type":"string"},"id":{"description":"The id of the next of kin","type":"string"},"last_name":{"description":"Last Name of the User's kin","type":"string"},"location":{"properties":{"address_line_1":{"description":"First line of the next of kin address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line of the next of kin address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line of the next of kin address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line of the next of kin address","type":"string","x-nullable":true},"city":{"description":"The city of the next of kin address","type":"string","x-nullable":true},"country":{"description":"The country of the next of kin address","type":"string","x-nullable":true},"postcode":{"description":"The postcode of the next of kin address","type":"string","x-nullable":true}},"type":"object","x-nullable":true},"phone":{"description":"UK Phone of next of kin","type":"string"},"relationship":{"description":"Type of relationship","enum":["spouse","sibling","parent","child","sibling_of_parent","child_of_sibling","cousin","grandparent","step_sibling","step_parent","stepchild","sibling_in_law","parent_in_law","child_in_law","friend","partner","other"],"type":"string"}},"type":"object"},"type":"array"},"work_phone_number":{"description":"The work phone number for the user","type":"string","x-nullable":true},"work_email":{"description":"The work email of the user","type":"string","x-nullable":true},"create_employment_period":{"default":false,"description":"When true, creates a minimal employment period for the user. Requires administrative_org_unit to be set.","type":"boolean"},"exclude_from_payroll":{"description":"Whether to exclude the user from payroll integration","type":"boolean"},"identifies_as":{"description":"The gender the user identifies as","enum":["female","male","non_binary","not_entered","other"],"type":"string"},"receives_content_for_org_units":{"description":"Org units for which the user receives Newsfeed content. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"external_id":{"description":"The client specific external ID of a user","type":"string"},"auth_strategies":{"description":"Available methods for logging into the system","items":{"properties":{"id":{"description":"Ignored on creation — there is no existing strategy to match against yet.","type":"string"},"key":{"description":"The unique key used to identify the user for this strategy","type":"string"},"type":{"description":"The strategy type","enum":["email","mobile_number","okta","microsoft"],"type":"string"}},"required":["key","type"],"type":"object"},"type":"array"},"date_of_birth":{"description":"The DOB of the user","type":"string","x-nullable":true},"marital_status":{"description":"The marital status of the user","enum":["single","in_a_relationship","engaged","married","civil_partnership","separated","divorced","widowed","other"],"type":"string"},"administrative_org_unit":{"description":"The org unit the user will be associated with for reporting purposes. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.\n                  Required when create_employment_period is true.","properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object","x-nullable":true},"middle_name":{"description":"The middle name of the user","type":"string"},"last_name":{"description":"The last name of the user","type":"string"},"employment_period":{"description":"Employment period configuration. Only used when create_employment_period is true.","properties":{"continuous_employment_start_date":{"default":"9999-01-01","description":"ISO8601 date for continuous employment start. Defaults to start_date.","format":"date","type":"string"},"level_id":{"description":"The Sona unique identifier of the level. Defaults to lowest level in org hierarchy.","type":"string"},"start_date":{"default":"9999-01-01","description":"ISO8601 date for employment period start. Defaults to 9999-01-01.","format":"date","type":"string"}},"type":"object","x-nullable":true},"bank_account":{"description":"Bank Account details. Send as null to remove.","properties":{"account_name":{"description":"Name of the Account","type":"string"},"account_number":{"description":"Account number","type":"string"},"bank_name":{"description":"Name of the Bank","type":"string"},"country_code":{"description":"Country Code - only GB is supported","enum":["GB"],"type":"string"},"sort_code":{"description":"Sort code of the bank account","type":"string"}},"required":["country_code","sort_code","account_number","account_name"],"type":"object","x-nullable":true},"manager_org_units":{"description":"Org units the user has full manager (write) access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"legal_first_name":{"description":"The legal first name of the user","type":"string","x-nullable":true},"title":{"description":"Title to refer to the user","enum":["mr","mrs","miss","dr","ms","mx"],"type":"string"},"contact_phone_number":{"description":"The contact phone number for the user","type":"string","x-nullable":true},"first_name":{"description":"The first name of the user","type":"string"},"appears_on_roster_for_org_units":{"description":"Org units for which the user should appear on the roster even if they don't have shifts. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"blocked_for_org_units":{"description":"Org units for which the user is blocked. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"assignable_org_units":{"description":"Org units for which this user can be assigned shifts by the manager. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"preferred_name":{"description":"The preferred name of the user","type":"string"},"contact_mobile_number":{"description":"The contact mobile number for the user","type":"string"}},"required":["last_name","first_name"],"title":"User Parameters","type":"object"},"CreateWorkRestrictionParameters":{"description":"Parameters for creating new work restrictions","example":{"is_night_worker":false,"is_under_ewtd":true,"is_under_student_visa":false,"worker_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"is_night_worker":{"description":"Whether the worker is designated as a night worker (EWTD Reg 6)","type":"boolean"},"is_under_ewtd":{"description":"Whether the worker is under the European Working Time Directive","type":"boolean"},"is_under_student_visa":{"description":"Whether the worker is under a student visa","type":"boolean"},"worker_id":{"description":"The ID of the worker the restriction is for","type":"string"}},"required":["worker_id"],"title":"Work restriction parameters","type":"object"},"CreateWorkingPreferenceParameters":{"description":"Parameters for creating new working preferences","example":{"days":[{"available":true,"name":"monday"},{"available":false,"name":"tuesday"},{"available":true,"name":"wednesday"},{"available":true,"name":"thursday"},{"available":true,"name":"friday"},{"available":false,"name":"saturday"},{"available":false,"name":"sunday"}],"worker_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"days":{"$ref":"#/definitions/WorkingPreferenceDays","description":"Days of the week the worker prefers to work"},"is_under_ewtd":{"description":"[DEPRECATED - use /api/v1/work_restrictions instead] Whether the worker is under the European Working Time Directive. Written to the worker's work restriction.","type":"boolean"},"is_under_student_visa":{"description":"[DEPRECATED - use /api/v1/work_restrictions instead] Whether the worker is under a student visa. Written to the worker's work restriction.","type":"boolean"},"worker_id":{"description":"The ID of the worker the preference is for","type":"string"}},"required":["days","worker_id"],"title":"Working preference parameters","type":"object"},"CustomForm":{"description":"A custom form definition. Use this endpoint to discover the fields required when creating entries via `POST /api/v1/custom_form_entries`.\n\nThe `schema` property contains the form's field definitions. Its shape depends on `schema_type`:\n\n- `legacy` — the fields are organised into **fieldsets**. Each fieldset has a `fields` array describing the individual fields (name, type, label, etc.). The rest of this description covers this shape.\n- `view_enabled` — `schema` holds a flat `fields` list, and `form_views` describes how those fields are presented, e.g. which questions the employee answers and which the manager answers.\n\n**Submission types**\n\n- `single` — the form has one fieldset. All fields in that fieldset are required when creating an entry.\n- `multiple` — the form has several fieldsets, each representing a different **document type** (e.g. \"CV\", \"Passport\"). The first fieldset is a header; the remaining fieldsets each have a `variation` object with a `value` key that identifies the document type. When creating an entry, pass the chosen document type as `employee_document_type` and include the fields from the matching fieldset.\n\n**How to read the schema**\n\nEach fieldset contains:\n- `fields` — an array of field objects, each with at least `name` (the key to use in the create request) and `type` (e.g. `\"text\"`, `\"date\"`, `\"file\"`)\n- `variation` (only on `multiple` forms) — an object with `value` (the document type identifier) and `label` (human-readable name)\n","example":{"form_name":"Employee Documents","form_views":[],"id":"159600fb-3fa9-4687-bbc9-196375de6ee3","organisation_id":"a3f1c2d4-5b6e-4f70-8a91-b2c3d4e5f607","schema":{"fieldsets":[{"fields":[{"input":"select","label":"Document type","name":"employee_document_type","readonly":false,"type":"string"}]},{"fields":[{"input":"text","label":"Description","name":"employee_documents_cv_description","readonly":false,"type":"string"},{"input":"file_upload","label":"Upload file","name":"employee_documents_cv_upload_file","readonly":false,"type":"url"}],"variation":{"label":"CV","value":"CV"}}]},"schema_type":"legacy","submission_type":"multiple"},"properties":{"current_version_id":{"description":"The published version the form views belong to, or null when the form has none","type":"string","x-nullable":true},"form_name":{"description":"Human-readable name for the custom form","type":"string","x-nullable":true},"form_views":{"description":"The current version's form views, ordered by slug. Empty when the form has no current version or its version has no views.","items":{"properties":{"description":{"description":"The view's description","type":"string"},"id":{"description":"The form view's identifier","type":"string"},"schema":{"description":"Groups of steps of questions, each question with its label, `required`, `required_conditions`, `conditions` (visibility), `editable` and `editable_conditions`","type":"object"},"slot_slug":{"description":"Where the view is used, e.g. `form_request` for a form sent to an employee to fill in","type":"string"},"slug":{"description":"The view's slug, unique within its version","type":"string"},"title":{"description":"The view's title","type":"string"}},"type":"object"},"type":"array"},"id":{"description":"The unique Sona identifier of the custom form","type":"string"},"organisation_id":{"description":"The unique Sona identifier of the organisation the form belongs to","type":"string"},"schema":{"description":"The form's field definitions: fieldsets for a `legacy` form, a flat `fields` list for a `view_enabled` form. Inspect this to discover which field keys to include when creating entries.","properties":{"fields":{"description":"Only on `view_enabled` forms: the flat list of field definitions, each with at least `name` and `type`.","items":{"type":"object"},"type":"array"},"fieldsets":{"description":"Array of fieldset objects. Each fieldset contains a `fields` array (field definitions) and, for `multiple` submission type forms, a `variation` object identifying the document type.","items":{"type":"object"},"type":"array"}},"type":"object"},"schema_type":{"description":"`legacy` (fields under `fieldsets`) or `view_enabled` (a flat `fields` list, presented through form views)","type":"string"},"submission_type":{"description":"Either `single` (one fieldset, no document type selection) or `multiple` (several fieldsets, each representing a different document type). Determines whether `employee_document_type` is required when creating entries.","type":"string","x-nullable":true}},"required":["id"],"title":"Custom Form","type":"object"},"CustomFormEntries":{"description":"A collection of Custom form entries","items":{"$ref":"#/definitions/CustomFormEntry"},"title":"Custom form entries","type":"array"},"CustomFormEntry":{"description":"A submitted entry for a custom form","example":{"audience_locks":{"employee":{"at":"2026-05-21T14:30:00Z","by_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb","by_name":"Sam Employee"},"manager":{"at":"2026-05-20T09:15:00Z","by_id":"b3a1e7b2-4d5f-4890-b12c-3e4f56789abc","by_name":"Alex Manager"}},"custom_form_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","data":{"employee_document_type":"CV","employee_documents_cv_description":"Uploaded curriculum vitae","employee_documents_cv_upload_file":[{"client_filename":"cv.pdf","client_size":48213,"client_type":"application/pdf","internal_filename":"6f1c2a9e-cv.pdf","storage_path":"custom_form_entries/6f1c2a9e-cv.pdf"}]},"id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","locked_at":"2026-05-21T14:30:00Z","user_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb"},"properties":{"audience_locks":{"description":"Per-audience signature stamps for forms that use audience-aware locking. Keys are audience identifiers (e.g. `\"manager\"`, `\"employee\"`); values record when and by whom that audience signed. Empty object for forms that don't configure audiences.","type":"object"},"custom_form_id":{"description":"The unique Sona identifier of the custom form this entry was created against.","type":"string"},"data":{"description":"An object containing the submitted field values, keyed by the field names from the custom form's schema. For file upload fields, a successful upload returns an array of objects with `client_filename`, `internal_filename`, `storage_path`, `client_type`, and `client_size`. Fields that were not provided in the request will appear as `null`.","type":"object"},"id":{"description":"Auto-generated unique identifier for this entry. This is NOT the user ID — it is a new UUID created by Sona when the entry is saved.","type":"string"},"locked_at":{"description":"ISO 8601 timestamp recording when the entry was fully locked. `null` until every audience configured on the form has signed (or, for whole-form locking, until any `lock_on_completion` trigger fires). Locked entries are treated as immutable snapshots by the Sona UI.","type":"string","x-nullable":true},"user_id":{"description":"The unique Sona identifier of the user this entry belongs to. This is the same `user_id` you sent in the create request.","type":"string"}},"required":["custom_form_id","user_id","id"],"title":"Custom Form Entry","type":"object"},"CustomForms":{"description":"A collection of Custom Forms","items":{"$ref":"#/definitions/CustomForm"},"title":"Custom Forms","type":"array"},"DeleteAbsenceResponse":{"description":"The absence that was deleted","example":{"id":"9f8bda89-002a-45e0-8660-46e2fff714d2","idx":"some-external-id"},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"idx":{"description":"An external ID for this absence period","type":"string","x-nullable":true}},"required":["id"],"title":"Delete absence","type":"object"},"DemandPeriodData":{"properties":{"end_time":{"description":"HH:MM","type":"string"},"id":{"description":"Demand period id","type":"string"},"name":{"description":"Name","type":"string"},"position":{"description":"Order, from 0","type":"integer"},"start_time":{"description":"HH:MM","type":"string"},"updated_at_utc":{"description":"UTC ISO 8601","type":"string"}},"title":"Demand period data","type":"object"},"DemandPeriodInput":{"description":"One demand period in the full list","properties":{"end_time":{"description":"HH:MM. May be earlier than start_time for an overnight period.","type":"string"},"id":{"description":"Id of an existing period of this service. Omit to create a new one.","type":"string"},"name":{"description":"Period name, unique within the service","type":"string"},"start_time":{"description":"HH:MM","type":"string"}},"required":["end_time","start_time","name"],"title":"Demand period input","type":"object"},"DemandPeriodRef":{"properties":{"id":{"description":"Demand period id","type":"string"},"name":{"description":"Name","type":"string"}},"title":"Demand period reference","type":"object"},"DemandPeriodsParams":{"description":"The complete list. Periods you leave out are deleted.","example":{"demand_periods":[{"end_time":"19:00","id":"7a1f0c9e-2b3d-4c5e-8f60-1a2b3c4d5e6f","name":"Day","start_time":"07:00"},{"end_time":"07:00","name":"Night","start_time":"19:00"}]},"properties":{"demand_periods":{"description":"All demand periods for the service","items":{"$ref":"#/definitions/DemandPeriodInput"},"type":"array"}},"required":["demand_periods"],"title":"Demand periods params","type":"object"},"DetailedErrorResponse":{"description":"The `ErrorResponse` shape, but `message` is not always a string: depending on the failure it is a string, a list of strings, or an object mapping field names to an error message or a list of messages.","example":{"error":"Unprocessable Entity","message":{"starts_at":["can't be blank"]}},"properties":{"error":{"description":"HTTP status phrase (e.g. \"Unprocessable Entity\")","type":"string"},"message":{"description":"A string, a list of strings, or an object mapping field names to an error message or a list of messages","x-type":["string","array","object"]}},"required":["error","message"],"title":"Error response with details","type":"object"},"EmploymentPeriod":{"description":"A period of employment for a User of the Sona System","example":{"active":true,"continuous_employment_start_date":"2020-06-15","contract_id":"0b1e4b8e-2c3d-4e5f-8a9b-1c2d3e4f5a6b","employee_number":"EMP-00472","finish_date":"2030-03-31","forces_new_holiday_breakdown":false,"id":"159600fb-3fa9-4687-bbc9-196375de6ee3","idx":"EXT-EP-4521","job_id":"3214f01b-7264-4c79-846a-8583a75e1d70","level_id":"befc811b-f379-4472-9f28-31cb41add989","organisation_id":"a3f1c2d4-5b6e-4f70-8a91-b2c3d4e5f607","organisation_user_id":"7fe0866b-9631-42cb-94b1-d8e426ac0fb0","primary_org_unit":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"Maple Ward"},"reports_to_org_user":{"id":"84f0d476-f2d6-43c4-adbf-b7f952b0af5f","name":"Jane Smith","work_email":"jane.smith@example.com"},"reports_to_user_id":"18c32388-0a1e-442a-9a2f-3e0621cc068a","start_date":"2024-03-01","starter_declaration":"A","student_loan_plan_five":false,"student_loan_plan_four":false,"student_loan_plan_one":false,"student_loan_plan_two":true,"student_loan_post_graduate":false,"tupe":false,"updated_at_utc":"2025-01-10T09:30:00Z"},"properties":{"active":{"description":"Whether or not the user is currently active","type":"boolean"},"continuous_employment_start_date":{"description":"","format":"date","type":"string"},"contract_id":{"description":"The unique Sona identifier of the contract this employment period belongs to. Employment periods sharing a contract_id are versions of the same contract.","type":"string"},"employee_number":{"description":"Unique number of the employee who is working this employment_period","type":"string","x-nullable":true},"employment_specs":{"description":"related employment specs","items":{"$ref":"#/definitions/EmploymentSpec"},"type":"array"},"finish_date":{"description":"The finish date for this period of employment, or null when it is open-ended","format":"date","type":"string","x-nullable":true},"forces_new_holiday_breakdown":{"description":"Whether to create a new holiday breakdown starting with this employment period. Defaults to true if the new employment period is for a rehire.","type":"boolean","x-nullable":true},"groups":{"description":"related groups","items":{"$ref":"#/definitions/Group"},"type":"array"},"id":{"description":"The unique Sona identifier of the employment_period","type":"string"},"idx":{"description":"External identifier of the employment_period","type":"string","x-nullable":true},"job_id":{"description":"The unique Sona identifier of the job","type":"string","x-nullable":true},"level_id":{"description":"The unqiue Soan identifier of the level","type":"string","x-nullable":true},"organisation_id":{"description":"Sona unique identifier of the organisation","type":"string"},"organisation_user_id":{"description":"[DEPRECATED] For backwards compatible reason, this parameter is still available.\nThe unique Sona identifier of the organisation user record\n","type":"string"},"permission_roles":{"description":"related permission roles","items":{"$ref":"#/definitions/PermissionRole"},"type":"array"},"primary_org_unit":{"description":"User's primary org unit, or null when none is set","properties":{"id":{"description":"The unique Sona identifier for the org unit","type":"string"},"name":{"description":"The name of the org unit","type":"string"}},"title":"Primary Location","type":"object","x-nullable":true},"reports_to_org_user":{"description":"Manager information, or null when the employee has no manager","properties":{"id":{"description":"The unique Sona identifier for the manager org user","type":"string"},"name":{"description":"Manager's first and last name","type":"string"},"work_email":{"description":"Manager's work email address","type":"string","x-nullable":true}},"title":"Reports to Org User","type":"object","x-nullable":true},"reports_to_user_id":{"description":"The unique Sona identifier of the user to whom the employee reports","type":"string","x-nullable":true},"start_date":{"description":"The start date for this period of employment","format":"date","type":"string"},"starter_declaration":{"description":"Starter declaration (https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1106535/Starter_checklist.pdf), or null when not recorded","enum":["A","B","C"],"type":"string","x-nullable":true},"student_loan_plan_five":{"description":"Employee has student loan plan five","type":"boolean","x-nullable":true},"student_loan_plan_four":{"description":"Employee has student loan plan four","type":"boolean","x-nullable":true},"student_loan_plan_one":{"description":"Employee has student loan plan one","type":"boolean","x-nullable":true},"student_loan_plan_two":{"description":"Employee has student loan plan two","type":"boolean","x-nullable":true},"student_loan_post_graduate":{"description":"Employee has post graduate student loan plan","type":"boolean","x-nullable":true},"termination_reason":{"$ref":"#/definitions/EmploymentPeriodTerminationReason","description":"The reason this user's employment was terminated, or null when not terminated","x-nullable":true},"termination_would_rehire":{"description":"Whether or not this person is eligible for rehiring following termination","type":"boolean","x-nullable":true},"tupe":{"description":"Whether the employee has transferred under TUPE regulations","type":"boolean","x-nullable":true},"types_of_work":{"description":"related types of work","items":{"$ref":"#/definitions/TypeOfWork"},"type":"array"},"updated_at_utc":{"description":"When the employment period was last updated, in UTC","format":"date-time","type":"string"}},"required":["student_loan_post_graduate","student_loan_plan_five","student_loan_plan_four","student_loan_plan_two","student_loan_plan_one","starter_declaration","start_date","reports_to_user_id","level_id","job_id","id","finish_date","employee_number","continuous_employment_start_date","active"],"title":"Employment Period","type":"object"},"EmploymentPeriodParameters":{"description":"A period of employment for a User of the Sona System","example":{"active":true,"continuous_employment_start_date":"2019-07-20","employee_number":"A568675","employment_specs":[{"contract_types":[{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}],"contracted_hours":[{"days":10,"finish_date":"2020-04-20","hours":30.0,"idx":"external id","monthly_period_starts_at":1,"org_unit_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","period":"weekly","rolling_period_sequence_length_days":20,"rolling_period_sequence_start":"2024-03-01","start_date":"2019-04-20","type_of_work_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","weekly_period_starts_at":"tuesday"}],"rates_of_pay":[{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},{"amount":1000,"currency":"GBP","finish_date":"2025-04-20","idx":"external id","is_default":true,"pay_type":"salaried","start_date":"2019-04-20"}]}],"finish_date":"2020-04-20","forces_new_holiday_breakdown":false,"idx":"external idx","job_grade_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","job_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","level_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","permission_roles":[{"id":"84f0d476-f2d6-43c4-adbf-b7f952b0af5f"},{"id":"18c32388-0a1e-442a-9a2f-3e0621cc068a"}],"primary_org_unit_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","probation_end_date":"2019-07-20","reports_to_user_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","start_date":"2019-04-20","starter_declaration":"A","student_loan_plan_five":false,"student_loan_plan_four":false,"student_loan_plan_one":true,"student_loan_plan_two":false,"student_loan_post_graduate":false,"tupe":false,"types_of_work":[{"id":"3214f01b-7264-4c79-846a-8583a75e1d70"},{"id":"15bb52f6-7f37-4ac8-84ee-71fbca32106d"}],"user_id":"7fe0866b-9631-42cb-94b1-d8e426ac0fb0"},"properties":{"types_of_work":{"description":"Types of work to assign, by Sona unique identifier","items":{"properties":{"id":{"description":"Sona unique identifier of the type of work","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"primary_org_unit_idx":{"description":"External ID of the primary org unit, as an alternative to `primary_org_unit_id`.","type":"string"},"start_date":{"description":"The start date for this period of employment","format":"date","type":"string"},"permission_roles":{"description":"Permission roles to assign, by Sona unique identifier","items":{"properties":{"id":{"description":"Sona unique identifier of the permission role","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"tupe":{"description":"Whether the employee has transferred under TUPE regulations","type":"boolean"},"contract_id":{"description":"The unique Sona identifier of the contract the new employment period continues (only on creation; ignored on update). Requires the overlapping employment periods feature to be enabled for the organisation. Must be the contract_id of one of the same person's existing employment periods, and is required when starts_new_contract is false or omitted. Must not be passed with starts_new_contract: true. Organisations without the feature must omit it: the contract is managed automatically.","type":"string"},"user_id":{"description":"The unique Sona identifier of the user entity","type":"string"},"termination_reason_id":{"description":"The unique Sona identifier of the termination reason","type":"string"},"starts_new_contract":{"description":"Whether the new employment period starts a new contract with an auto-generated contract_id (only on creation). Requires the overlapping employment periods feature to be enabled for the organisation. When false or omitted, contract_id is required. Organisations without the feature must omit it.","type":"boolean"},"student_loan_plan_two":{"description":"Employee has student loan plan two","type":"boolean"},"employment_specs":{"description":"Related employment specs. Required on create (at least one must be provided with contract_types, rates_of_pay, and contracted_hours). Optional on update.","items":{"$ref":"#/definitions/EmploymentSpecParams"},"type":"array"},"probation_end_date":{"description":"The date that probation ends","format":"date","type":"string"},"idx":{"description":"External identifier of the employment_period","type":"string"},"employee_number":{"description":"Unique number of the employee who is working this employment_period","type":"string"},"pay_group_id":{"description":"Sona's unique identifier or external ID of the employee's pay group. Only honoured on creation; ignored on update.","type":"string"},"student_loan_post_graduate":{"description":"Employee has post graduate student loan plan","type":"boolean"},"continuous_employment_start_date":{"description":"","format":"date","type":"string"},"student_loan_plan_four":{"description":"Employee has student loan plan four","type":"boolean"},"groups":{"description":"related groups","items":{"$ref":"#/definitions/Group"},"type":"array"},"id":{"description":"The unique Sona identifier of the employment_period. Auto-generated on create.","type":"string"},"termination_reason_details":{"description":"The reason why this user's period of employment has ended","type":"string"},"job_id":{"description":"The unique Sona identifier of the job","type":"string"},"termination_would_rehire":{"description":"Whether or not this person is eligible for rehiring following termination","type":"boolean"},"job_grade_id":{"description":"The unique Sona identifier of the job grade","type":"string"},"student_loan_plan_one":{"description":"Employee has student loan plan one","type":"boolean"},"primary_org_unit_id":{"description":"The Sona unique identifier of the primary org unit. Pass either this or `primary_org_unit_idx`.","type":"string"},"student_loan_plan_five":{"description":"Employee has student loan plan five","type":"boolean"},"level_id":{"description":"The unique Sona identifier for the employee's level","type":"string"},"reports_to_user_id":{"description":"The unique Sona identifier of the **user** (not the organisation_user) to whom the employee reports. Use the `user_id` from the `/api/v1/users` endpoint.","type":"string"},"starter_declaration":{"description":"Starter declaration (https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1106535/Starter_checklist.pdf)","enum":["A","B","C"],"type":"string"},"active":{"description":"The employment period's own `active` flag (not the user's). On create it is saved, defaulting to false. On update it is not saved: sending `false` only skips reconciling the period's types of work, which the leaver process relies on.","type":"boolean"},"finish_date":{"description":"The finish date for this period of employment","format":"date","type":"string"},"forces_new_holiday_breakdown":{"description":"Whether to create a new holiday breakdown starting with this employment period. Defaults to true if the new employment period is for a rehire.","type":"boolean"}},"required":["continuous_employment_start_date","user_id","start_date"],"title":"Employment Period","type":"object"},"EmploymentPeriodPermissionRolesUpdateResponse":{"description":"Response for updating permission roles associated with an employment period","example":{"permission_roles":[{"description":"Manages ward-level scheduling and staff assignments","id":"c4f7a312-8e1b-4d6a-9f3c-2a5b8e0d1f4a","location_specific":true,"name":"Ward Manager"}],"permission_roles_locations":[{"org_unit_ids":["a1b2c3d4-5678-4def-abcd-111111111111","d4e5f6a7-8901-4bcd-efab-333333333333"],"permission_role_id":"c4f7a312-8e1b-4d6a-9f3c-2a5b8e0d1f4a"}]},"properties":{"permission_roles":{"description":"The updated permission roles for the employment period","items":{"$ref":"#/definitions/PermissionRole"},"type":"array"},"permission_roles_locations":{"$ref":"#/definitions/PermissionRolesLocations","description":"Mapping of location specific permission roles to org units"}},"title":"Employment Period Permission Roles Update Response","type":"object"},"EmploymentPeriodRateOfPay":{"description":"A rate of pay","example":{"finish_date":"2025-03-01","id":"b4d2f8c3-5e6a-4901-c23d-4f5a67890bcd","idx":"ROP-EXT-001","is_default":false,"label":"Standard Rate","pay_type":"hourly","rate":"£12.50","start_date":"2024-03-01"},"properties":{"finish_date":{"description":"The date from when this rate of pay is no longer applicable, or null when open-ended","format":"date","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the rate of pay","type":"string"},"idx":{"description":"An external identifier of the rate of pay.","type":"string","x-nullable":true},"is_default":{"description":"Whether this is a default rate of pay","type":"boolean"},"label":{"description":"A label for the rate of pay","type":"string","x-nullable":true},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string","x-nullable":true},"pay_type":{"default":"hourly","description":"How it is paid, e.g. hourly / salaried","enum":["hourly","salaried"],"type":"string"},"rate":{"description":"The payment rate, formatted with its currency symbol (e.g. \"£12.50\")","type":"string"},"start_date":{"description":"The date from when this rate of pay is applicable","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type_of_work","type":"string","x-nullable":true}},"required":["rate","finish_date","start_date","id"],"title":"Rate of Pay","type":"object"},"EmploymentPeriodSimple":{"description":"A summary representation of an employment period","example":{"active":true,"continuous_employment_start_date":"2020-06-15","contract_id":"0b1e4b8e-2c3d-4e5f-8a9b-1c2d3e4f5a6b","employee_number":"EMP-00472","finish_date":"2030-03-31","forces_new_holiday_breakdown":false,"id":"159600fb-3fa9-4687-bbc9-196375de6ee3","idx":"EXT-EP-4521","job_grade_id":"15bb52f6-7f37-4ac8-84ee-71fbca32106d","job_id":"3214f01b-7264-4c79-846a-8583a75e1d70","level_id":"befc811b-f379-4472-9f28-31cb41add989","organisation_id":"a3f1c2d4-5b6e-4f70-8a91-b2c3d4e5f607","organisation_user_id":"7fe0866b-9631-42cb-94b1-d8e426ac0fb0","primary_org_unit_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","raffy_tier":"raffy_gm","reports_to_org_user_id":"84f0d476-f2d6-43c4-adbf-b7f952b0af5f","reports_to_user_id":"18c32388-0a1e-442a-9a2f-3e0621cc068a","start_date":"2024-03-01","starter_declaration":"A","student_loan_plan_five":false,"student_loan_plan_four":false,"student_loan_plan_one":false,"student_loan_plan_two":false,"student_loan_post_graduate":false,"tupe":false,"updated_at_utc":"2025-01-10T09:30:00Z","user_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb"},"properties":{"types_of_work":{"description":"related types of work","items":{"$ref":"#/definitions/TypeOfWork"},"type":"array"},"start_date":{"description":"The start date for this period of employment","format":"date","type":"string"},"permission_roles":{"description":"Related permission roles","items":{"$ref":"#/definitions/PermissionRole"},"type":"array"},"raffy_tier":{"description":"The user's current Raffy credit tier: raffy_crew, raffy_gm, or raffy_hq","type":"string","x-nullable":true},"tupe":{"description":"Whether the employee has transferred under TUPE regulations","type":"boolean","x-nullable":true},"contract_id":{"description":"The unique Sona identifier of the contract this employment period belongs to. Employment periods sharing a contract_id are versions of the same contract.","type":"string"},"user_id":{"description":"The unique Sona identifier of the user record","type":"string"},"student_loan_plan_two":{"description":"Employee has student loan plan two","type":"boolean","x-nullable":true},"employment_specs":{"description":"related employment specs","items":{"$ref":"#/definitions/EmploymentSpec"},"type":"array"},"idx":{"description":"External identifier of the employment_period","type":"string","x-nullable":true},"employee_number":{"description":"Unique number of the employee who is working this employment_period","type":"string","x-nullable":true},"permission_roles_locations":{"$ref":"#/definitions/PermissionRolesLocations"},"organisation_id":{"description":"Sona unique identifier of the organisation","type":"string"},"student_loan_post_graduate":{"description":"Employee has post graduate student loan plan","type":"boolean","x-nullable":true},"continuous_employment_start_date":{"description":"","format":"date","type":"string"},"student_loan_plan_four":{"description":"Employee has student loan plan four","type":"boolean","x-nullable":true},"id":{"description":"The unique Sona identifier of the employment_period","type":"string"},"reports_to_org_user_id":{"description":"[DEPRECATED: use reports_to_user_id] The unique Sona identifier of the organisation user to whom the employee reports","type":"string","x-nullable":true},"job_id":{"description":"The unique Sona identifier of the job","type":"string","x-nullable":true},"updated_at_utc":{"description":"When the employment period was last updated, in UTC","format":"date-time","type":"string"},"termination_would_rehire":{"description":"Whether or not this person is eligible for rehiring following termination","type":"boolean","x-nullable":true},"job_grade_id":{"description":"The unique Sona identifier of the job grade","type":"string","x-nullable":true},"student_loan_plan_one":{"description":"Employee has student loan plan one","type":"boolean","x-nullable":true},"termination_reason":{"$ref":"#/definitions/EmploymentPeriodTerminationReason","description":"The reason this users employment was terminated","x-nullable":true},"primary_org_unit_id":{"description":"The Sona unique identifier of the org unit","type":"string"},"student_loan_plan_five":{"description":"Employee has student loan plan five","type":"boolean","x-nullable":true},"level_id":{"description":"The unqiue Soan identifier of the level","type":"string","x-nullable":true},"reports_to_user_id":{"description":"The unique Sona identifier of the user to whom the employee reports","type":"string","x-nullable":true},"starter_declaration":{"description":"Starter declaration (https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1106535/Starter_checklist.pdf)","type":"string","x-nullable":true},"active":{"description":"Whether or not the user is currently active","type":"boolean"},"finish_date":{"description":"The finish date for this period of employment, or null when it is open-ended","format":"date","type":"string","x-nullable":true},"forces_new_holiday_breakdown":{"description":"Whether to create a new holiday breakdown starting with this employment period. Defaults to true if the new employment period is for a rehire.","type":"boolean","x-nullable":true},"organisation_user_id":{"description":"The unique Sona identifier of the organisation user record","type":"string"}},"required":["employee_number","student_loan_post_graduate","student_loan_plan_five","student_loan_plan_four","student_loan_plan_two","student_loan_plan_one","starter_declaration","continuous_employment_start_date","reports_to_user_id","reports_to_org_user_id","primary_org_unit_id","job_grade_id","job_id","level_id","user_id","organisation_user_id","raffy_tier","active","finish_date","start_date","id"],"title":"Employment Period","type":"object"},"EmploymentPeriodTerminationReason":{"description":"The reason this users employment was terminated","example":{"details":"Employee relocated to another region","id":"d6f4b0e5-7a8c-4123-e45f-6b7c89012def","name":"Resignation"},"properties":{"details":{"description":"Additional context for the termination","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the termination reason record, or null when only `details` were recorded","type":"string","x-nullable":true},"name":{"description":"The reason, or null when only `details` were recorded","type":"string","x-nullable":true}},"required":["id"],"title":"EmploymentPeriodTerminationReason","type":"object"},"EmploymentPeriods":{"description":"A collection of Employment Periods","items":{"$ref":"#/definitions/EmploymentPeriodSimple"},"title":"Employment Periods","type":"array"},"EmploymentPeriodsIndex":{"description":"A paginated list of employment periods","example":{"page_number":1,"page_size":25,"total_entries":87,"total_pages":4},"properties":{"entries":{"$ref":"#/definitions/EmploymentPeriods"},"page_number":{"description":"The current page number (1-indexed)","type":"integer"},"page_size":{"description":"The number of items per page (default: 10)","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"title":"Employment Periods Index","type":"object"},"EmploymentSpec":{"description":"An employment spec for an employment period. Note: the `employment_type` field (with values `full_time`, `part_time`, or `flexible`) is a property of the nested contract type, not a direct field on the employment period or employment spec.","example":{"contract_types":[{"employment_type":"full_time","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","name":"Full Time","pay_period_start":"monday","pay_period_type":"weekly","payroll_rule":"hourly"}],"contracted_hours":[{"days":5,"finish_date":"2025-03-01","hours":37.5,"id":"c5e3a9d4-6f7b-4012-d34e-5a6b78901cde","monthly_period_starts_at":1,"period":"weekly","start_date":"2024-03-01","weekly_period_starts_at":"monday"}],"id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","rates_of_pay":[{"finish_date":"2025-03-01","id":"b4d2f8c3-5e6a-4901-c23d-4f5a67890bcd","pay_type":"hourly","rate":"£12.50","start_date":"2024-03-01"}]},"properties":{"contract_types":{"description":"Related contract types. Use `api/v1/contract_types` endpoint to retrieve available Contract Types. Each contract type includes an `employment_type` field with values: `full_time`, `part_time`, or `flexible`.","items":{"$ref":"#/definitions/ContractType"},"maxItems":1,"type":"array"},"contracted_hours":{"description":"related contracted hours","items":{"$ref":"#/definitions/ContractedHour"},"type":"array"},"id":{"description":"The unique Sona identifier of the employment_spec","type":"string"},"rates_of_pay":{"description":"related rates of pay. Use `api/v1/rates_of_pay endpoint to retrieve available Rates of Pay","items":{"$ref":"#/definitions/EmploymentPeriodRateOfPay"},"type":"array"}},"required":["id"],"title":"Employment Spec","type":"object"},"EmploymentSpecContractTypeBulkUpdateItem":{"description":"Single employment spec contract type update","example":{"new_contract_type_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","spec_id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},"properties":{"new_contract_type_id":{"description":"Sona unique identifier of the contract type","type":"string"},"spec_id":{"description":"Sona unique identifier of the employment_spec","type":"string"}},"required":["new_contract_type_id","spec_id"],"title":"Employment spec contract type update item","type":"object"},"EmploymentSpecContractTypeBulkUpdateParams":{"description":"Bulk update contract types for employment specs","example":[{"new_contract_type_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","spec_id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}],"items":{"$ref":"#/definitions/EmploymentSpecContractTypeBulkUpdateItem"},"title":"Employment spec contract type bulk update params","type":"array"},"EmploymentSpecContractTypeBulkUpdateResponse":{"description":"Bulk update result","example":{"errors":[{"employment_spec_id":"befc811b-f379-4472-9f28-31cb41add989","errors":{"contract_type_id":["does not exist"]},"index":2}],"updated_count":3},"properties":{"errors":{"items":{"description":"Errors for a failed update. Validation errors include index; update errors include employment_spec_id.","properties":{"employment_spec_id":{"description":"Employment spec identifier for update-time errors","type":"string"},"errors":{"description":"Errors for the failed update","type":"object"},"index":{"description":"Index of the failed update in the request array","type":"integer"}},"type":"object"},"type":"array"},"updated_count":{"description":"Number of employment specs updated","type":"integer"}},"title":"Employment spec contract type bulk update response","type":"object"},"EmploymentSpecContractTypeUpdateParams":{"description":"Employment spec contract type update parameters","example":{"contract_type_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93"},"properties":{"contract_type_id":{"description":"Sona unique identifier of the contract type","type":"string"}},"required":["contract_type_id"],"title":"Employment spec contract type update params","type":"object"},"EmploymentSpecParams":{"description":"An employment spec for an employment period","example":{"contract_types":[{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}],"contracted_hours":[{"days":5,"finish_date":"2025-03-01","hours":37.5,"idx":"CH-EXT-001","monthly_period_starts_at":1,"period":"weekly","start_date":"2024-03-01","weekly_period_starts_at":"monday"}],"id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","rates_of_pay":[{"amount":1250,"currency":"GBP","finish_date":"2025-03-01","idx":"ROP-EXT-001","pay_type":"hourly","start_date":"2024-03-01"}]},"properties":{"contract_types":{"description":"Required. Exactly one contract type must be provided per employment spec.","items":{"$ref":"#/definitions/ContractTypeParams"},"maxItems":1,"type":"array"},"contracted_hours":{"description":"Related contracted hours. Required on create (at least one must be provided). Not updatable via this endpoint.","items":{"$ref":"#/definitions/ContractedHourParams"},"type":"array"},"id":{"description":"The unique Sona identifier of the employment_spec","type":"string"},"rates_of_pay":{"description":"Related rates of pay. Required on create (at least one must be provided). Not updatable via this endpoint.","items":{"$ref":"#/definitions/RateOfPayParams"},"type":"array"}},"title":"Employment Spec params","type":"object"},"ErrorOrValidationErrors":{"description":"One of two shapes: `ValidationErrors` (`{ \"errors\": { field: [messages] } }`) for field validation failures, or `DetailedErrorResponse` (`{ \"error\": ..., \"message\": ... }`) for other failures. Check for `errors` first.","example":{"errors":{"name":["can't be blank"]}},"properties":{"error":{"description":"HTTP status phrase","type":"string"},"errors":{"description":"Field names mapped to lists of error messages","type":"object"},"message":{"description":"A string, a list of strings, or an object mapping field names to an error message or a list of messages"}},"title":"Error response or validation errors","type":"object","x-oneOf":[{"$ref":"#/definitions/ValidationErrors"},{"$ref":"#/definitions/DetailedErrorResponse"}]},"ErrorResponse":{"description":"Standard error response returned by the API for common non-validation errors (400, 401, 403, 404, 500). Some endpoints also use this shape for `422`, while validation failures may return a different schema.\n\n**Common error codes:**\n- `401 Unauthorized`: Missing or invalid Bearer token.\n- `403 Forbidden`: Your API token does not have permission to access this endpoint. API tokens can be restricted to specific endpoints — contact Sona to have additional endpoints added to your token's allowed list.\n- `404 Not Found`: The requested resource does not exist or belongs to a different organisation.\n- `422 Unprocessable Entity`: The request contained invalid parameters. The response body may use this schema or a validation-specific schema depending on the endpoint.\n- `500 Internal Server Error`: An unexpected error occurred on the server.","example":{"error":"Unprocessable Entity","message":"The provided input is invalid"},"properties":{"error":{"description":"HTTP status phrase (e.g. \"Not Found\", \"Unprocessable Entity\")","type":"string"},"message":{"description":"Human-readable error detail","type":"string"}},"required":["message","error"],"title":"Error Response","type":"object"},"ExternalMapping":{"description":"A single external mapping","example":{"id":"29afd7cc-00b5-473a-83a1-5c8b59223fab","idx":"external_id_of_variable_type","source_type":"general","user_id":"19497f54-29de-47a9-9bda-1b84bcdc0de3"},"properties":{"absence_type_id":{"description":"Sona unique identifier of of an absence type","type":"string","x-nullable":true},"contract_type_id":{"description":"Sona unique identifier of a contract type. Set only on `dayforce_pay_policy` mappings","type":"string","x-nullable":true},"id":{"description":"The unique identifier of the external mapping","type":"string"},"idx":{"description":"An identifier for the external mapping","type":"string"},"job_id":{"description":"Sona unique identifier of a job","type":"string","x-nullable":true},"level_id":{"description":"Sona unique identifier of a level","type":"string","x-nullable":true},"org_unit_id":{"description":"Sona unique identifier of an org unit","type":"string","x-nullable":true},"pay_group_id":{"description":"Sona unique identifier of a pay group","type":"string","x-nullable":true},"permission_role_id":{"description":"Sona unique identifier of a permission role","type":"string","x-nullable":true},"skill_attribute_id":{"description":"Sona unique identifier of a skill attribute","type":"string","x-nullable":true},"source_type":{"description":"Type of source that external mapping is relates to","type":"string"},"termination_reason_id":{"description":"Sona unique identifier of a termination reason. Set only on `dayforce_termination_reason` mappings","type":"string","x-nullable":true},"user_id":{"description":"Sona unique identifier of the user for this external mapping","type":"string","x-nullable":true}},"title":"External mapping","type":"object"},"ExternalMappings":{"description":"A collection of external mappings","items":{"$ref":"#/definitions/ExternalMapping"},"title":"External mappings","type":"array"},"FailForecastRunParameters":{"description":"Parameters for failing a forecast run","example":{"ended_at":"2026-04-27T09:14:32Z","error_message":"Forecast pipeline timed out"},"properties":{"ended_at":{"description":"ISO8601 timestamp when the forecast failed","type":"string"},"error_message":{"description":"Optional failure detail","type":"string"}},"required":["ended_at"],"title":"FailForecastRunParameters","type":"object"},"ForecastRun":{"description":"A forecast run record","example":{"ended_at":"2026-04-27T09:14:32.000000Z","id":"3f2b6a1e-8c4d-4e7a-9b1f-2d5c6e7f8a90","organisation_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","started_at":"2026-04-27T09:00:00.000000Z","status":"succeeded","target":"revenue","train_wape":"7.66"},"properties":{"ended_at":{"description":"ISO8601 timestamp; null while running","type":"string","x-nullable":true},"error_message":{"description":"Failure detail; null unless failed","type":"string","x-nullable":true},"id":{"description":"The forecast run id","type":"string"},"organisation_id":{"description":"The organisation id","type":"string"},"started_at":{"description":"ISO8601 timestamp","type":"string"},"status":{"description":"Forecast run status","enum":["running","succeeded","failed"],"type":"string"},"target":{"description":"The forecast target","type":"string"},"train_wape":{"description":"Train WAPE percentage between 0 and 100, as a decimal string; null until known","type":"string","x-nullable":true}},"title":"ForecastRun","type":"object"},"ForecastRunResponse":{"description":"Wrapper for a forecast run response","example":{"data":{"ended_at":"2026-04-27T09:14:32.000000Z","id":"3f2b6a1e-8c4d-4e7a-9b1f-2d5c6e7f8a90","organisation_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","started_at":"2026-04-27T09:00:00.000000Z","status":"succeeded","target":"revenue","train_wape":"7.66"}},"properties":{"data":{"$ref":"#/definitions/ForecastRun","description":"The forecast run record"}},"title":"ForecastRunResponse","type":"object"},"ForecastRunTrainWapeParameters":{"description":"Parameters for backfilling forecast train WAPE","example":{"train_wape":7.66},"properties":{"train_wape":{"description":"Train WAPE percentage between 0 and 100","type":"number"}},"required":["train_wape"],"title":"ForecastRunTrainWapeParameters","type":"object"},"ForecastRunsResponse":{"description":"Paginated forecast runs response","properties":{"entries":{"description":"Forecast run records","items":{"$ref":"#/definitions/ForecastRun"},"type":"array"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"title":"ForecastRunsResponse","type":"object"},"Group":{"description":"A Group","example":{"id":"e7a5c1f6-8b9d-4234-f56a-7c8d90123ef0","name":"Night Shift Team"},"properties":{"id":{"description":"The unique Sona identifier of the Group record","type":"string"},"name":{"description":"The name of the group","type":"string"}},"required":["id"],"title":"Group","type":"object"},"HolidayAdjustment":{"description":"Holiday adjustment","example":{"active_at":"2024-10-25","amount":1.0,"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","id":"79a6a0bd-2fbc-4f86-8a25-5f810d04026c","reason":"reason","type":"allowance","units":"days","user_id":"ea72e3cc-b8c6-433a-87e3-d14520e21430"},"properties":{"active_at":{"description":"Date identifying when the adjustment is applied","type":"string"},"amount":{"description":"Number of hours/days to adjust","format":"float","type":"number"},"contract_id":{"description":"The unique Sona identifier of the contract of the record's absence year, or null for an absence year without a contract","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the rate of pay","type":"string"},"reason":{"description":"Reason for adjustment","type":"string"},"type":{"description":"Type of adjustment","enum":["allowance","taken_holiday"],"type":"string"},"units":{"description":"Adjustment unit","enum":["hours","days"],"type":"string"},"user_id":{"description":"The unique ID of the user the adjustment is for","type":"string"}},"title":"Holiday adjustment","type":"object"},"HolidayAdjustmentCreateParams":{"description":"Holiday adjustment params","example":{"active_at":"2024-10-25","amount":1,"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","reason":"reason","type":"allowance","user_id":"0a727402-fcae-44ee-bb6e-b7d88e334e87","variation":"add"},"properties":{"active_at":{"description":"Date identifying when the adjustment is applied","type":"string"},"amount":{"description":"Number of hours or days to adjust. The unit is determined by the denomination of the absence year","format":"float","type":"number"},"contract_id":{"description":"The Sona unique identifier of the contract the request is about, from `contract_id` on `GET /api/v1/employment_periods`. Only used by organisations with overlapping employment periods enabled. Required when the user holds employment periods on several contracts covering the requested date; without it the only contract covering that date is used. A contract whose employment periods have ended still reads its own absence year covering the date.","type":"string"},"reason":{"description":"Reason for adjustment","type":"string"},"type":{"description":"Type of adjustment","enum":["allowance","taken_holiday"],"type":"string"},"user_id":{"description":"The unique ID of the user the adjustment is for","type":"string"},"variation":{"description":"Add or deduct allowance, use `\"add\"` when creating adjustments of type `\"taken_holiday\"`","enum":["add","deduct"],"type":"string"}},"required":["type","variation","amount","reason","user_id"],"title":"Holiday adjustment params","type":"object"},"HolidayAdjustments":{"description":"A collection of holiday adjustments","items":{"$ref":"#/definitions/HolidayAdjustment"},"title":"Holiday adjustments","type":"array"},"HolidayBalance":{"description":"Details of the user's holiday balance","example":{"booked":2,"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","denomination":"days","pending":5,"remaining":1,"taken":10},"properties":{"booked":{"description":"Number of holidays the user has booked","type":"number"},"contract_id":{"description":"The unique Sona identifier of the contract of the absence year, or null for an absence year without a contract","type":"string","x-nullable":true},"denomination":{"description":"Unit of holiday measurement (e.g., days)","type":"string"},"pending":{"description":"Number of holidays pending approval for the user","type":"number"},"remaining":{"description":"Number of holidays remaining for the user","type":"number"},"taken":{"description":"Number of holidays the user has taken","type":"number"}},"title":"User Holiday Balance","type":"object"},"HourLineData":{"properties":{"assignment_count":{"description":"People supported in the ratio","type":"integer"},"demand_periods":{"description":"The demand periods it applies in","items":{"$ref":"#/definitions/DemandPeriodRef"},"type":"array"},"hours":{"description":"Commissioned hours","type":"number"},"id":{"description":"Hour line id","type":"string"},"org_unit_id":{"description":"Service id","type":"string"},"org_unit_idx":{"description":"Service `upload_id`","type":"string","x-nullable":true},"staff_count":{"description":"Staff in the ratio","type":"integer"},"status":{"description":"`pending`, `pre_approved`, `approved` or `rejected`: the latest approval status","type":"string"},"type_of_work_id":{"description":"Care type id","type":"string"},"type_of_work_idx":{"description":"Care type `idx`","type":"string","x-nullable":true}},"title":"Hour line data","type":"object"},"HourLineInput":{"description":"One commissioned hours line","properties":{"assignment_count":{"description":"People supported in the ratio, above 0","type":"integer"},"demand_period_ids":{"description":"Ids of this service's demand periods. At least one.","items":{"type":"string"},"type":"array"},"hours":{"description":"Commissioned hours, above 0","type":"number"},"org_unit_id":{"description":"Service (Sona id). Give this or `org_unit_idx`.","type":"string"},"org_unit_idx":{"description":"Service (your own id, `upload_id`)","type":"string"},"staff_count":{"description":"Staff in the ratio, above 0","type":"integer"},"type_of_work_id":{"description":"Care type (Sona id). Give this or `type_of_work_idx`.","type":"string"},"type_of_work_idx":{"description":"Care type (your own id: the care type's `idx`)","type":"string"}},"required":["demand_period_ids","assignment_count","staff_count","hours"],"title":"Hour line input","type":"object"},"HourStatusCreateParams":{"example":{"effective_date":"2026-10-12","notes":"Signed off by the commissioner","status":"approved"},"properties":{"effective_date":{"description":"ISO 8601 date the status takes effect. Defaults to today (the UTC date). Not after today in UTC, not before the package starts, and not before the line's current status.","type":"string"},"notes":{"description":"Free text","type":"string","x-nullable":true},"status":{"description":"`pending`, `pre_approved`, `approved` or `rejected`","type":"string"}},"required":["status"],"title":"Hour status create params","type":"object"},"HourStatusData":{"example":{"actioned_by_source":"api","effective_date":"2026-10-12","hour_id":"8f933d28-b46d-489d-add9-8d22396f239e","id":"5d0c2b1a-9e8f-4a7b-b6c5-d4e3f2a1b0c9","inserted_at_utc":"2026-10-12T09:00:00Z","notes":"Signed off by the commissioner","status":"approved"},"properties":{"actioned_by_source":{"description":"`api` for statuses added through this API","type":"string"},"effective_date":{"description":"ISO 8601 date","type":"string"},"hour_id":{"description":"The hour line","type":"string"},"id":{"description":"Status id","type":"string"},"inserted_at_utc":{"description":"When it was added, UTC ISO 8601","type":"string"},"notes":{"description":"Notes","type":"string","x-nullable":true},"status":{"description":"`pending`, `pre_approved`, `approved` or `rejected`","type":"string"}},"title":"Hour status data","type":"object"},"HourStatusDataList":{"items":{"$ref":"#/definitions/HourStatusData"},"title":"Hour statuses","type":"array"},"InterpolationPreviewRequest":{"description":"Parameters for previewing rule interpolation.","example":{"from_date":"2024-06-15","rules":[{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":"{avg_hourly_footfall} / 20"},"rule_type":"inflexible_demand_linear"}],"to_date":"2024-07-15"},"properties":{"from_date":{"description":"Start date (ISO 8601, YYYY-MM-DD)","type":"string"},"rules":{"description":"Optional override rules with rule_type, rule_params, master_shift_type_id, metric_id","items":{"type":"object"},"type":"array"},"strategy_id":{"description":"Optional UUID of a labour strategy. When set, rules are resolved per date from the strategy's group assignments (status-agnostic). Mutually exclusive with `rules`.","type":"string"},"to_date":{"description":"End date (ISO 8601, YYYY-MM-DD)","type":"string"}},"required":["to_date","from_date"],"title":"Interpolation Preview Request","type":"object"},"InterpolationPreviewResponse":{"description":"Interpolated rules grouped by date.","example":{"dates":[{"attributes":{"avg_hourly_footfall":"145.5"},"date":"2024-06-15","rules":[{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":7.275},"rule_type":"inflexible_demand_linear"}]}]},"properties":{"dates":{"description":"List of date entries with attributes and concrete rules","items":{"type":"object"},"type":"array"}},"required":["dates"],"title":"Interpolation Preview Response","type":"object"},"Job":{"description":"A role or position within the organisation. Jobs are assigned to employment periods and used for scheduling. A job can be archived when no longer in use.","example":{"archived_at":"2024-10-29T09:14:57Z","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example job","upload_id":"Example upload id"},"properties":{"archived_at":{"description":"The timestamp the job was archived at in ISO8601 format","type":"string","x-nullable":true},"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the job","type":"string"},"upload_id":{"description":"External identifier used during CSV bulk import (nullable)","type":"string","x-nullable":true}},"required":["upload_id","name","id"],"title":"Job","type":"object"},"Jobs":{"description":"A collection of Jobs","items":{"$ref":"#/definitions/Job"},"title":"Jobs","type":"array"},"Level":{"description":"A hierarchical classification within an organisation, such as a seniority band or pay grade. Levels can be nested via a parent reference to form a tree structure (e.g. Senior > Mid > Junior).","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"An example level"},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the level","type":"string"},"parent":{"$ref":"#/definitions/Level","description":"The parent level","x-nullable":true}},"required":["name","id"],"title":"Level","type":"object"},"Levels":{"description":"A collection of Levels","items":{"$ref":"#/definitions/Level"},"title":"Levels","type":"array"},"LinkedShifts":{"description":"A collection of shift ids, start and end datetime that are contained within an absence period","items":{"$ref":"#/definitions/AbsenceShifts"},"title":"Linked shifts","type":"array"},"LockedPeriod":{"description":"A locked period representing a time range during which the roster for a location is locked and no further roster changes can be made.","example":{"end_datetime":"2024-08-31T23:59:59Z","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","org_unit_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","organisation_id":"f0e1d2c3-b4a5-6789-abcd-012345678901","start_datetime":"2024-08-01T00:00:00Z"},"properties":{"end_datetime":{"description":"End of the locked period. in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"org_unit_id":{"description":"Unique identifier of the location (org unit)","type":"string"},"organisation_id":{"description":"Unique identifier of the organisation","type":"string"},"start_datetime":{"description":"Start of the locked period. in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"}},"required":["organisation_id","org_unit_id","end_datetime","start_datetime","id"],"title":"Locked period","type":"object"},"LockedPeriods":{"description":"A collection of locked periods","items":{"$ref":"#/definitions/LockedPeriod"},"title":"Locked periods","type":"array"},"Metric":{"description":"A single metric definition configured for the organisation","example":{"calculation_interval_minutes":30,"has_actuals":true,"has_human_forecasts":true,"has_last_year":false,"has_system_forecasts":true,"id":"019d3000-1dcf-7be2-8e71-7111130259ae","name":"Food Revenue","unit":"gbp"},"properties":{"calculation_interval_minutes":{"description":"The size of the buckets the metric is recorded in, in minutes","enum":[15,30,60,120,180,1440],"type":"integer"},"has_actuals":{"description":"Whether the organisation records actual values","type":"boolean"},"has_human_forecasts":{"description":"Whether the organisation records human-forecasted (manager) values","type":"boolean"},"has_last_year":{"description":"Whether Sona records `last_year` values for this metric (the actual from the same date one year earlier, stored as its own value type)","type":"boolean"},"has_system_forecasts":{"description":"Whether the organisation records system-forecasted values","type":"boolean"},"id":{"description":"Sona unique identifier of the metric","type":"string"},"name":{"description":"Customer-defined label for the metric, e.g. \"Food Revenue\". Not stable across organisations — match on `type` instead if you need to identify what the metric represents.","type":"string"},"type":{"description":"The metric's meaning, from a fixed vocabulary, where Sona recognises it as one of its standard metrics. Stable across organisations, but null for metrics the customer defined themselves — which includes most stored revenue metrics.","type":"string","x-nullable":true},"unit":{"description":"The unit the metric is measured in","enum":["items","gbp","people","percentage","usd","eur"],"type":"string"}},"required":["name","id"],"title":"Metric","type":"object"},"MetricCalculationEntry":{"description":"A distinct metric calculation. All listed properties are always present; nullable fields appear with value `null` when unset. The `ast` field carries a normalised, language-agnostic representation of the formula so consumers can evaluate it without parsing the Sona DSL.","example":{"aggregation_behaviour":"formula","applied_at":["00000000-0000-0000-0000-000000000aaa","00000000-0000-0000-0000-000000000bbb"],"ast":{"root":{"left":{"left":{"name":"wage_actual_cash","type":"var"},"op":"/","right":{"name":"cascade_revenue","type":"var"},"type":"binop"},"op":"*","right":{"type":"const","value":100},"type":"binop"}},"canonical_metric_definition":{"description":"Wage cost as a percentage of actual revenue.","display_label":"Actual Wage %","expected_unit":"percentage","slug":"wage_pct_actual"},"formula":"(wage_actual_cash / cascade_revenue) * 100","name":"Wage % - Actual","unit":"percentage","variables":[{"aggregation_behaviour":"sum","binding_type":"cascade","cascade_order":["actual","human_forecasted","system_forecasted","budget"],"granularity_minutes":1440,"metric_id":"abc12345-0000-0000-0000-000000000001","metric_name":"Revenue","metric_type":"wage_cost","unit":"gbp","variable":"cascade_revenue"},{"aggregation_behaviour":"sum","binding_type":"actual","filter":{"interval":{"begins_inside_ends_inside":true,"begins_inside_ends_outside":true,"begins_outside_ends_inside":true,"begins_outside_ends_outside":true,"end":"15:00:00","start":"12:00:00"},"name":"Lunch service","type":"time_interval"},"granularity_minutes":1440,"metric_id":"abc12345-0000-0000-0000-000000000002","metric_name":"Wage Cost (Actual)","metric_type":"wage_cost","unit":"gbp","variable":"wage_actual_cash"}]},"properties":{"aggregation_behaviour":{"description":"How the calculation aggregates across the rollup","enum":["sum","formula","none"],"type":"string"},"applied_at":{"description":"Sorted list of org_unit UUIDs whose productivity_config uses this calculation","type":"array"},"ast":{"$ref":"#/definitions/MetricCalculationsAst","description":"Normalised JSON AST of the formula. See the schema for the node grammar."},"canonical_metric_definition":{"$ref":"#/definitions/MetricCalculationsCanonicalDefinition","description":"Canonical metric concept this calculation is mapped to (nullable). Null when the customer's admin has not assigned a canonical concept to this calc; the calc is then customer-specific and won't be referenced by cross-customer report definitions.","x-nullable":true},"expanded_formula":{"description":"Expanded form of the formula (often null)","type":"string","x-nullable":true},"formula":{"description":"Raw Sona formula DSL string (e.g. `(wage_actual_cash / cascade_revenue) * 100`)","type":"string"},"name":{"description":"Display name of the calculation","type":"string"},"ratio_denominator_name":{"description":"Name of the denominator calculation when this is a ratio. See ratio_numerator_name for why we use names not UUIDs.","type":"string","x-nullable":true},"ratio_numerator_name":{"description":"When this calculation is a ratio, the name of the numerator calculation. Sona's data model gives each productivity_config its own UUID for the same logical calc, so deduplication compares by name (semantic identity) not UUID.","type":"string","x-nullable":true},"unit":{"description":"Numeric unit","enum":["items","gbp","people","percentage","usd","eur","count"],"type":"string"},"variables":{"description":"Variable bindings used by this calculation's formula","items":{"$ref":"#/definitions/MetricCalculationVariable"},"type":"array"}},"required":["applied_at","ast","variables","aggregation_behaviour","unit","formula","name"],"title":"Metric Calculation Entry","type":"object"},"MetricCalculationVariable":{"description":"A variable binding in a calculation's formula. Exactly one of `metric_id` and `calc_name` is non-null per binding. Metric-bound variables (non-null `metric_id`) carry the full enrichment set: `binding_type`, `metric_name`, `metric_type`, `unit`, `granularity_minutes`, `aggregation_behaviour`, `filter`, plus `cascade_order` when `binding_type` is `\"cascade\"`. Calc-bound variables (non-null `calc_name`) carry `variable`, `metric_id` (null), `calc_name`, and `canonical_slug` (nullable; populated when the referenced calc has a canonical mapping).","example":{"aggregation_behaviour":"sum","binding_type":"actual","filter":{"interval":{"begins_inside_ends_inside":true,"begins_inside_ends_outside":true,"begins_outside_ends_inside":true,"begins_outside_ends_outside":true,"end":"15:00:00","start":"12:00:00"},"name":"Lunch service","type":"time_interval"},"granularity_minutes":1440,"metric_id":"abc12345-0000-0000-0000-000000000002","metric_name":"Wage Cost (Actual)","metric_type":"wage_cost","unit":"gbp","variable":"wage_actual_cash"},"properties":{"aggregation_behaviour":{"description":"Per-metric rollup behaviour (`sum`, `max`, `none`). Distinct from the entry-level `aggregation_behaviour`. Present only on metric-bound variables.","type":"string"},"binding_type":{"description":"Which leg of stored data the binding refers to. Only present on metric-bound variables.","enum":["actual","system_forecasted","human_forecasted","budget","cascade","last_year"],"type":"string"},"calc_name":{"description":"Name of another calculation this variable resolves to. Null when the variable references a raw metric.","type":"string","x-nullable":true},"canonical_slug":{"description":"Canonical slug of the referenced calculation when the variable is calc-bound AND the referenced calc has a canonical mapping. Null when the variable is metric-bound OR the referenced calc has no canonical mapping. Lets cross-customer report definitions resolve recursive-calc references by canonical slug without re-walking the catalogue.","type":"string","x-nullable":true},"cascade_order":{"description":"Ordered cascade leg names. Present only when `binding_type` is `\"cascade\"`. Resolution order: try the first leg; if no value, try the next; etc.","type":"array"},"filter":{"$ref":"#/definitions/MetricCalculationsFilter","description":"Polymorphic filter scoping the metric to specific time intervals or types of work. Null when no filter is set; absent for calc-bound variables.","x-nullable":true},"granularity_minutes":{"description":"Storage interval of the metric in minutes (one of 15, 30, 60, 120, 180, 1440). Present only on metric-bound variables.","type":"integer"},"metric_id":{"description":"UUID of the underlying metric this variable resolves to. Null when the variable references another calculation.","format":"uuid","type":"string","x-nullable":true},"metric_name":{"description":"Display name of the underlying metric (`Metric.name`). Present only on metric-bound variables.","type":"string"},"metric_type":{"description":"Sona metric type enum (e.g. `wage_cost`, `hours`, `hourly_pay`). Present only on metric-bound variables.","type":"string"},"unit":{"description":"Unit of the underlying metric (`items`, `gbp`, `people`, `percentage`, `usd`, `eur`). Present only on metric-bound variables.","type":"string"},"variable":{"description":"Variable name as referenced in the formula string (e.g. `wage_actual_cash`)","type":"string"}},"required":["variable"],"title":"Metric Calculation Variable","type":"object"},"MetricCalculationsAst":{"description":"Normalised JSON tree representing the parsed formula. Grammar version lives at the response root (`schema_version`), not on this block. `root` is a recursive AST node — one of:\n\n- `{type: \"const\", value: <number>}`\n- `{type: \"var\", name: <string>}`\n- `{type: \"binop\", op: <\"+\"|\"-\"|\"*\"|\"/\">, left: <node>, right: <node>}`\n- `{type: \"neg\", operand: <node>}`\n- `{type: \"cmp\", op: <\"<\"|\"<=\"|\">\"|\">=\">, left: <node>, right: <node>}`\n- `{type: \"call\", fn: <\"abs\"|\"ceil\"|\"div\"|\"floor\"|\"if\"|\"max\"|\"min\"|\"rem\"|\"round\"|\"trunc\">, args: [<node>, ...]}` (required arity: 1 for abs/ceil/floor/round/trunc; 2 for div/rem/max/min; 3 for if)\n- `{type: \"bool\", value: <true|false>}`\n- `{type: \"logic\", op: <\"and\"|\"or\">, left: <node>, right: <node>}`\n- `{type: \"not\", operand: <node>}`\n- `{type: \"temporal_modifier\", shift: {kind, count?, unit, mode?}, expr: <node>}` — wraps `expr` to be evaluated against a shifted scope. `kind` is one of `prev|trailing|to_date`; `count` is a positive integer (required for `prev`/`trailing`, absent for `to_date`); `unit` is `day|week|month|quarter|year` in schema v3 (sub-day units reserved for the intra-day workstream); `mode` is optional (`calendar` is the default and is omitted; `fiscal_aligned` is valid only when `unit=year` in v3). Modifiers may nest.\n\n`var` nodes only reference names declared in the same entry's `variables`. Source-position metadata (line, column) is never present. Calculations whose formulas use syntax outside this grammar are omitted from the response and a warning is logged server-side.\n","example":{"root":{"left":{"left":{"name":"wage_actual_cash","type":"var"},"op":"/","right":{"name":"cascade_revenue","type":"var"},"type":"binop"},"op":"*","right":{"type":"const","value":100},"type":"binop"}},"properties":{"root":{"description":"Root AST node (recursive)","type":"object"}},"required":["root"],"title":"Metric Calculations AST","type":"object"},"MetricCalculationsCanonicalDefinition":{"description":"The canonical metric concept this calculation is mapped to. The `slug` is the stable cross-customer identifier reports reference. `display_label` is the canonical label rendered when the org's `reporting_label_preference` is `canonical`. `deprecated_at` carries the retirement timestamp when the concept is soft-deleted; `replacement_slug` then points at the active successor concept (nullable — null when no successor was wired). Existing mappings to deprecated concepts remain valid; new mappings to deprecated concepts are rejected at the changeset layer.","example":{"description":"Best-of-cascade revenue across the actual / forecast / budget legs (FC/ACT cascade); represents the most-recent reliable revenue value at report time.","display_label":"Cascade-aware Actual Revenue","expected_unit":"gbp","slug":"revenue_fc_act"},"properties":{"deprecated_at":{"description":"ISO 8601 UTC timestamp when the canonical concept was soft-deprecated. Null for active concepts.","type":"string","x-nullable":true},"description":{"description":"Semantic description of the canonical concept","type":"string"},"display_label":{"description":"Human-readable canonical label","type":"string"},"expected_unit":{"description":"Canonical numeric unit","enum":["gbp","percentage","hours","items"],"type":"string"},"replacement_slug":{"description":"Slug of the active replacement concept; populated only when `deprecated_at` is non-null AND a replacement was wired pointing at an active concept.","type":"string","x-nullable":true},"slug":{"description":"Stable canonical identifier (lowercase snake_case)","type":"string"}},"required":["expected_unit","description","display_label","slug"],"title":"Metric Calculations Canonical Definition","type":"object"},"MetricCalculationsFilter":{"description":"Polymorphic filter mirroring Sona's `MetricFilter` shape. `type` discriminator selects the variant: `time_interval` carries `name` + `interval` (a TimeIntervalPayload); `types_of_work` carries `name` + `type_of_work_ids` (a list of UUIDs); `master_shift_types` carries `name` + `master_shift_type_ids` (a list of UUIDs); `time_interval_types_of_work` carries all three; `attribute_ref` carries `name` + `attribute_id`; `time_interval_attribute_ref` carries `name` + `interval` + `attribute_id`; `master_shift_type_group_ref` carries `name` + `master_shift_type_group_id`; `time_interval_master_shift_type_group_ref` carries `name` + `interval` + `master_shift_type_group_id`. `null` is returned when the binding has no filter. The four boundary booleans on `interval` decide how shifts crossing the interval edge are counted; consumers MUST honour them when evaluating filters.","example":{"interval":{"begins_inside_ends_inside":true,"begins_inside_ends_outside":true,"begins_outside_ends_inside":true,"begins_outside_ends_outside":true,"end":"15:00:00","start":"12:00:00"},"name":"Lunch service","type":"time_interval"},"properties":{"attribute_id":{"description":"`Attribute` UUID the filter scopes to. Present when `type` is `attribute_ref` or `time_interval_attribute_ref`.","type":"string"},"interval":{"$ref":"#/definitions/MetricCalculationsTimeIntervalPayload","description":"Time-interval payload. Present when `type` is one of `time_interval`, `time_interval_types_of_work`, `time_interval_attribute_ref`, `time_interval_master_shift_type_group_ref`."},"master_shift_type_group_id":{"description":"`MasterShiftTypeGroup` UUID the filter scopes to. Present when `type` is `master_shift_type_group_ref` or `time_interval_master_shift_type_group_ref`.","type":"string"},"master_shift_type_ids":{"description":"List of `master_shift_type` UUIDs the filter scopes to. Present when `type` is `master_shift_types`.","type":"array"},"name":{"description":"Display name of the filter (`MetricFilter.name`)","type":"string"},"type":{"description":"Filter discriminator","enum":["time_interval","types_of_work","master_shift_types","time_interval_types_of_work","attribute_ref","time_interval_attribute_ref","master_shift_type_group_ref","time_interval_master_shift_type_group_ref"],"type":"string"},"type_of_work_ids":{"description":"List of `type_of_work` UUIDs the filter scopes to. Present when `type` is `types_of_work` or `time_interval_types_of_work`.","type":"array"}},"required":["name","type"],"title":"Metric Calculations Filter","type":"object"},"MetricCalculationsOrgCalendar":{"description":"Organisation calendar config for evaluating temporal modifiers. `week_start_day` is a lowercase weekday token (monday|tuesday|…|sunday). `day_start_time` is the day-boundary time in 24-hour `HH:MM` form. `fiscal_year_start` is `MM-DD` with no year component.","example":{"day_start_time":"04:00","fiscal_year_start":"04-01","week_start_day":"monday"},"properties":{"day_start_time":{"description":"Day-boundary time in 24-hour HH:MM form","type":"string"},"fiscal_year_start":{"description":"Fiscal-year start as MM-DD (no year component)","type":"string"},"week_start_day":{"description":"Lowercase weekday token marking the first day of the week","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"required":["fiscal_year_start","day_start_time","week_start_day"],"title":"Metric Calculations Org Calendar","type":"object"},"MetricCalculationsResponse":{"description":"Wrapper for the deduplicated metric_calculations of one organisation. `schema_version` is the grammar version (currently `5`). The `org_calendar` block carries the calendar config consumers need to evaluate temporal shifts (`prev`, `trailing`, `to_date`) against the right scope — it MUST be used in the same snapshot as the returned `calculations`.","example":{"calculations":[{"aggregation_behaviour":"formula","applied_at":["00000000-0000-0000-0000-000000000aaa","00000000-0000-0000-0000-000000000bbb"],"ast":{"root":{"left":{"left":{"name":"wage_actual_cash","type":"var"},"op":"/","right":{"name":"cascade_revenue","type":"var"},"type":"binop"},"op":"*","right":{"type":"const","value":100},"type":"binop"}},"canonical_metric_definition":{"description":"Wage cost as a percentage of actual revenue.","display_label":"Actual Wage %","expected_unit":"percentage","slug":"wage_pct_actual"},"formula":"(wage_actual_cash / cascade_revenue) * 100","name":"Wage % - Actual","unit":"percentage","variables":[{"aggregation_behaviour":"sum","binding_type":"cascade","cascade_order":["actual","human_forecasted","system_forecasted","budget"],"granularity_minutes":1440,"metric_id":"abc12345-0000-0000-0000-000000000001","metric_name":"Revenue","metric_type":"wage_cost","unit":"gbp","variable":"cascade_revenue"},{"aggregation_behaviour":"sum","binding_type":"actual","filter":{"interval":{"begins_inside_ends_inside":true,"begins_inside_ends_outside":true,"begins_outside_ends_inside":true,"begins_outside_ends_outside":true,"end":"15:00:00","start":"12:00:00"},"name":"Lunch service","type":"time_interval"},"granularity_minutes":1440,"metric_id":"abc12345-0000-0000-0000-000000000002","metric_name":"Wage Cost (Actual)","metric_type":"wage_cost","unit":"gbp","variable":"wage_actual_cash"}]}],"fetched_at":"2026-05-06T14:30:00Z","org_calendar":{"day_start_time":"04:00","fiscal_year_start":"04-01","week_start_day":"monday"},"organisation_id":"13649e97-0506-4218-818b-6eca6d890677","reporting_label_preference":"display_name","schema_version":5},"properties":{"calculations":{"description":"Distinct metric calculations, deduplicated by content","items":{"$ref":"#/definitions/MetricCalculationEntry"},"type":"array"},"fetched_at":{"description":"ISO 8601 UTC timestamp when this response was generated","type":"string"},"org_calendar":{"$ref":"#/definitions/MetricCalculationsOrgCalendar","description":"Calendar config — week start, day-boundary time, fiscal-year start. Consumers MUST use this with `calculations` as a single snapshot."},"organisation_id":{"description":"UUID of the organisation","format":"uuid","type":"string"},"reporting_label_preference":{"description":"Organisation-level preference for the label rendered in reports. `display_name` = render the customer's preferred calc display name; `canonical` = render the canonical concept's `display_label`. Existing customers backfill to `display_name`; new customers default to `canonical`.","enum":["canonical","display_name"],"type":"string"},"schema_version":{"description":"Grammar version of the response payload","enum":[5],"type":"integer"}},"required":["calculations","reporting_label_preference","org_calendar","fetched_at","organisation_id","schema_version"],"title":"Metric Calculations Response","type":"object"},"MetricCalculationsTimeIntervalPayload":{"description":"A clock-time interval with boundary semantics. Times are rendered in canonical 24-hour ISO 8601 partial-time form `HH:MM:SS`. The four boundary booleans are load-bearing for evaluation: each decides whether shifts of a given boundary topology are included.\n","example":{"begins_inside_ends_inside":true,"begins_inside_ends_outside":true,"begins_outside_ends_inside":true,"begins_outside_ends_outside":true,"end":"15:00:00","start":"12:00:00"},"properties":{"begins_inside_ends_inside":{"description":"Include shifts whose start AND end are inside the interval?","type":"boolean"},"begins_inside_ends_outside":{"description":"Include shifts whose start is inside, end is outside?","type":"boolean"},"begins_outside_ends_inside":{"description":"Include shifts whose start is outside, end is inside?","type":"boolean"},"begins_outside_ends_outside":{"description":"Include shifts whose start AND end are outside the interval?","type":"boolean"},"end":{"description":"Interval end (HH:MM:SS, 24-hour)","type":"string"},"start":{"description":"Interval start (HH:MM:SS, 24-hour)","type":"string"}},"required":["end","begins_outside_ends_outside","begins_outside_ends_inside","begins_inside_ends_outside","begins_inside_ends_inside","start"],"title":"Metric Calculations Time Interval Payload","type":"object"},"MetricValue":{"description":"A single metric value","example":{"current":true,"current_start_timestamp":"2024-06-15T09:12:44Z","id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","metric_id":"019d3000-1dcf-7be2-8e71-7111130259ae","org_unit_id":"6d8e2f10-3b4a-4c5d-8e9f-0a1b2c3d4e5f","organisation_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","timestamp":"2024-06-15T07:00:00Z","type":"actual","value":12.5},"properties":{"current":{"description":"Whether this is the current value for its timestamp","type":"boolean"},"current_finish_timestamp":{"description":"When this value stopped being current (ISO 8601, UTC); null while current","type":"string","x-nullable":true},"current_start_timestamp":{"description":"When this value became current (ISO 8601, UTC)","type":"string"},"id":{"description":"Sona unique identifier of the stored value","type":"string"},"metric_id":{"description":"Sona unique identifier of the metric","type":"string"},"org_unit_id":{"description":"Sona unique identifier of the org unit","type":"string"},"organisation_id":{"description":"Sona unique identifier of the organisation","type":"string"},"timestamp":{"description":"Start of the bucket the value is for (ISO 8601, UTC)","type":"string"},"type":{"description":"The value type","enum":["actual","system_forecasted","human_forecasted","budget","last_year"],"type":"string"},"value":{"type":"number"}},"title":"MetricValue","type":"object"},"MetricValueParams":{"description":"A single metric value","example":{"timestamp":"2024-06-15T07:00:00Z","value":12.5},"properties":{"id":{"type":"string"},"timestamp":{"type":"string"},"value":{"type":"number"}},"title":"MetricValue","type":"object"},"Metrics":{"description":"A collection of Metrics","items":{"$ref":"#/definitions/Metric"},"title":"Metrics","type":"array"},"MetricsErrorResponse":{"description":"Error returned by the metric store and latest-timestamp endpoints themselves. Unlike `ErrorResponse` it has no `message`; `error` holds the detail. Authentication and endpoint-access failures still return `ErrorResponse`.","example":{"error":"type must be one of: actual, system_forecasted, human_forecasted, budget"},"properties":{"error":{"description":"What went wrong","type":"string"}},"required":["error"],"title":"Metrics Error Response","type":"object"},"MetricsForbiddenResponse":{"description":"One of two shapes: `ErrorResponse` when the API token can't use this endpoint, or `MetricsErrorResponse` (`{error}` only) when the values could not be stored.","example":{"error":"Unable to store metric values"},"properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"title":"Metrics forbidden response","type":"object","x-oneOf":[{"$ref":"#/definitions/ErrorResponse"},{"$ref":"#/definitions/MetricsErrorResponse"}]},"NextOfKinLocation":{"description":"Next of kin location data","example":{"address_line_1":"North Leigh","address_line_2":"Witney OX29 6PN","city":"Witney","country":"United Kingdom","postcode":"RH5C 4QF"},"properties":{"address_line_1":{"description":"First line of the next of kin address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line of the next of kin address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line of the next of kin address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line of the next of kin address","type":"string","x-nullable":true},"city":{"description":"The city of the next of kin address","type":"string","x-nullable":true},"country":{"description":"The country of the next of kin address","type":"string","x-nullable":true},"id":{"description":"The id of location","type":"string"},"postcode":{"description":"The postcode of the next of kin address","type":"string","x-nullable":true}},"title":"Next of kin location data","type":"object","x-nullable":true},"OrgAttribute":{"description":"An organisation-level attribute definition","example":{"description":"The time the location opens for service","id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","inserted_at":"2024-06-15T10:30:00","label":"Opening Time","slug":"opening_time","type":"time","updated_at":"2024-06-15T10:30:00","value":"07:00"},"properties":{"description":{"description":"Description of the attribute","type":"string","x-nullable":true},"id":{"description":"Unique identifier","format":"uuid","type":"string"},"inner_type":{"description":"Inner type for weekday attributes","type":"string","x-nullable":true},"inserted_at":{"description":"Created timestamp, in UTC without a timezone designator (e.g. 2024-06-15T10:30:00)","type":"string"},"label":{"description":"Display name","type":"string"},"slug":{"description":"Immutable snake_case identifier","type":"string"},"type":{"description":"Attribute type: time, boolean, integer, float, string, weekday","type":"string"},"updated_at":{"description":"Updated timestamp, in UTC without a timezone designator (e.g. 2024-06-15T10:30:00)","type":"string"},"value":{"description":"Default value for the attribute","type":"string","x-nullable":true}},"required":["label","slug","id"],"title":"Org Attribute","type":"object"},"OrgAttributeCreateParams":{"example":{"description":"The time the location opens for service","label":"Opening Time","slug":"opening_time","type":"time","value":"07:00"},"properties":{"description":{"description":"Description","type":"string"},"inner_type":{"description":"Inner type for weekday attributes","type":"string"},"label":{"description":"Display name","type":"string"},"slug":{"description":"Snake_case identifier (auto-generated from label if omitted)","type":"string"},"type":{"description":"Attribute type","type":"string"},"value":{"description":"Default value","type":"string"}},"required":["type","label"],"title":"Org Attribute Create Params","type":"object"},"OrgAttributeUpdateParams":{"example":{"description":"The time the location opens for service each day","label":"Opening Time (Updated)","type":"time","value":"08:00"},"properties":{"description":{"description":"Description","type":"string"},"inner_type":{"description":"Inner type for weekday attributes","type":"string"},"label":{"description":"Display name","type":"string"},"type":{"description":"Attribute type","type":"string"},"value":{"description":"Default value","type":"string"}},"title":"Org Attribute Update Params","type":"object"},"OrgAttributes":{"description":"A collection of org attributes","example":{"entries":[{"id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","inserted_at":"2024-06-15T10:30:00","label":"Opening Time","slug":"opening_time","type":"time","updated_at":"2024-06-15T10:30:00","value":"07:00"}],"page_number":1,"page_size":25,"total_entries":3,"total_pages":1},"properties":{"entries":{"description":"Org attributes","items":{"$ref":"#/definitions/OrgAttribute"},"type":"array"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"required":["entries","total_entries","total_pages","page_size","page_number"],"title":"Org Attributes","type":"object"},"OrgUnit":{"description":"A single org unit","example":{"accounting_cost_code":"cost code","average_hourly_rate":100.1,"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","is_base_entity":true,"name":"An example org unit","org_unit_type":{"id":"e27ad021-aa0e-4ccf-91e8-1c3c84626c91","name":"Region"},"parent_org_unit":{"id":"e27ad021-aa0e-4ccf-91e8-1c7c84656c93"},"paycaptain_company_id":"paycaptain id","public_holidays":[{"country_code":"GB","date":"2024-06-03","day_of_week":"Monday","is_substitute":true,"name":"Some Random Holiday","regions":["England","Scotland"]}]},"properties":{"accounting_cost_code":{"description":"Accounting cost code","type":"string","x-nullable":true},"average_hourly_rate":{"description":"Average hourly rate","format":"float","type":"number","x-nullable":true},"external_connections":{"items":{"properties":{"external_id":{"description":"External identifier of the org unit","type":"string"}},"type":"object"},"type":"array"},"id":{"description":"Sona unique identifier","type":"string"},"is_base_entity":{"description":"Whether it's a base entity","type":"boolean"},"name":{"description":"The name of the org unit","type":"string"},"org_unit_type":{"properties":{"id":{"description":"Sona unique identifier of the org unit type","type":"string"},"name":{"description":"The name of the org unit type","type":"string"}},"type":"object"},"parent_org_unit":{"properties":{"id":{"description":"Sona unique identifier of the parent org unit, or null for a top-level org unit","type":"string","x-nullable":true}},"type":"object"},"paycaptain_company_id":{"description":"Pay captain company id","type":"string","x-nullable":true},"public_holidays":{"items":{"$ref":"#/definitions/PublicHoliday"},"type":"array"},"timezone":{"description":"The timezone of the org unit","type":"string"}},"title":"OrgUnit","type":"object"},"OrgUnitAgencies":{"description":"A collection pay agencies","items":{"$ref":"#/definitions/OrgUnitAgency"},"title":"Agencies","type":"array"},"OrgUnitAgency":{"description":"A single agency","example":{"id":"e96de533-85c6-46d4-af7a-1f054783741e","name":"Agency ACME."},"properties":{"id":{"description":"Sona agency unique identifier","type":"string"},"name":{"description":"The name of the agency","type":"string"}},"required":["name","id"],"title":"Agency","type":"object"},"OrgUnitAttribute":{"description":"An org unit-level attribute value override","example":{"end_date":"2024-12-31","excluded":false,"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","inserted_at":"2024-06-15T08:00:00","org_attribute":{"id":"b2c3d4e5-6789-4abc-bcde-222222222222","label":"Average hourly footfall","slug":"average_hourly_footfall","type":"float"},"org_attribute_id":"b2c3d4e5-6789-4abc-bcde-222222222222","org_unit_id":"a1b2c3d4-5678-4def-abcd-111111111111","start_date":"2024-06-15","updated_at":"2024-06-15T08:00:00","value":"145.5"},"properties":{"end_date":{"description":"Last day the value is active (inclusive)","format":"date","type":"string","x-nullable":true},"excluded":{"description":"Whether this attribute is excluded for this org unit","type":"boolean"},"id":{"description":"Unique identifier","format":"uuid","type":"string"},"inserted_at":{"description":"Created timestamp, in UTC but without a timezone designator (`YYYY-MM-DDTHH:mm:ss`)","type":"string"},"org_attribute":{"$ref":"#/definitions/OrgAttribute","description":"The associated org attribute definition. Only `id`, `slug`, `label`, `type` and `inner_type` are included"},"org_attribute_id":{"description":"Org attribute ID","format":"uuid","type":"string"},"org_unit_id":{"description":"Org unit ID","format":"uuid","type":"string"},"start_date":{"description":"When this value becomes active (inclusive)","format":"date","type":"string","x-nullable":true},"updated_at":{"description":"Updated timestamp, in UTC but without a timezone designator (`YYYY-MM-DDTHH:mm:ss`)","type":"string"},"value":{"description":"The attribute value, or null when the attribute is excluded","type":"string","x-nullable":true}},"required":["org_attribute_id","org_unit_id","id"],"title":"Org Unit Attribute","type":"object"},"OrgUnitAttributeCreateParams":{"example":{"end_date":"2024-12-31","excluded":false,"org_attribute_id":"b2c3d4e5-6789-4abc-bcde-222222222222","org_unit_id":"a1b2c3d4-5678-4def-abcd-111111111111","start_date":"2024-06-15","value":"145.5"},"properties":{"end_date":{"description":"Last day the value is active (inclusive)","format":"date","type":"string"},"excluded":{"description":"Set to true to exclude this attribute for this org unit","type":"boolean"},"org_attribute_id":{"description":"Org attribute ID","format":"uuid","type":"string"},"org_unit_id":{"description":"Org unit ID","format":"uuid","type":"string"},"start_date":{"description":"When this value becomes active (inclusive)","format":"date","type":"string"},"value":{"description":"The attribute value (required unless excluded is true)","type":"string"}},"required":["org_attribute_id","org_unit_id"],"title":"Org Unit Attribute Create Params","type":"object"},"OrgUnitAttributeUpdateParams":{"example":{"end_date":"2025-03-31","excluded":false,"start_date":"2024-07-01","value":"160.0"},"properties":{"end_date":{"description":"Last day the value is active (inclusive)","format":"date","type":"string"},"excluded":{"description":"Whether this attribute is excluded","type":"boolean"},"start_date":{"description":"When this value becomes active (inclusive)","format":"date","type":"string"},"value":{"description":"The attribute value","type":"string"}},"title":"Org Unit Attribute Update Params","type":"object"},"OrgUnitAttributes":{"description":"A collection of org unit attributes","example":{"entries":[{"end_date":"2024-12-31","excluded":false,"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","org_attribute_id":"b2c3d4e5-6789-4abc-bcde-222222222222","org_unit_id":"a1b2c3d4-5678-4def-abcd-111111111111","start_date":"2024-06-15","value":"145.5"}],"page_number":1,"page_size":25,"total_entries":2,"total_pages":1},"properties":{"entries":{"description":"Org unit attributes","items":{"$ref":"#/definitions/OrgUnitAttribute"},"type":"array"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"required":["entries","total_entries","total_pages","page_size","page_number"],"title":"Org Unit Attributes","type":"object"},"OrgUnitCreateParams":{"description":"Org unit params","example":{"accounting_cost_code":"MW-001","average_hourly_rate":14.5,"external_connections":[{"external_id":"EXT-MW-001"}],"is_base_entity":true,"name":"Maple Ward","org_unit_type_id":"c9d80598-8223-4836-875d-f6dd9552a77f","parent_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","paycaptain_company_id":"PC-1234","public_holidays":["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]},"properties":{"accounting_cost_code":{"description":"Accounting cost code","type":"string"},"average_hourly_rate":{"description":"Average hourly rate","format":"float","type":"number"},"external_connections":{"items":{"properties":{"external_id":{"description":"External identifier of the org unit","type":"string"}},"type":"object"},"type":"array"},"is_base_entity":{"description":"Whether it's a base entity","type":"boolean"},"name":{"description":"The name of the org unit","type":"string"},"org_unit_type_id":{"description":"Sona unique identifier of the org unit type","type":"string"},"parent_id":{"description":"Sona unique identifier of the parent org unit","type":"string"},"paycaptain_company_id":{"description":"Pay captain company id","type":"string"},"public_holidays":{"items":{"type":"string"},"type":"array"}},"required":["is_base_entity","org_unit_type_id","name"],"title":"Org unit params","type":"object"},"OrgUnitFeatureSet":{"description":"The features for the organisational unit.","example":{"linked_shift_attendance_required":false,"shift_runs_enabled":false},"properties":{"linked_shift_attendance_required":{"description":"Whether or not the attendance is linked to the shifts.","type":"boolean"},"shift_runs_enabled":{"description":"Whether or not shift runs are enabled.","type":"boolean"}},"required":["linked_shift_attendance_required","shift_runs_enabled"],"title":"Organisational Unit Features","type":"object"},"OrgUnitFeatureSetParams":{"description":"The features params for the organisational unit.","example":{"linked_shift_attendance_required":false,"shift_runs_enabled":false},"properties":{"linked_shift_attendance_required":{"default":false,"description":"Whether or not the attendance is linked to the shifts.","type":"boolean"},"shift_runs_enabled":{"default":false,"description":"Whether or not shift runs are enabled.","type":"boolean"}},"title":"Organisational Unit Features Params","type":"object"},"OrgUnitLocation":{"description":"The location of an org unit","example":{"address_line_1":"1 Bridge Street","city":"London","country":"United Kingdom","country_code":"GB","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","latitude":51.5007,"longitude":-0.1246,"name":"Head Office","postcode":"SW1A0AA"},"properties":{"address_line_1":{"description":"The first line of address","type":"string","x-nullable":true},"address_line_2":{"description":"The second line of address","type":"string","x-nullable":true},"address_line_3":{"description":"The third line of address","type":"string","x-nullable":true},"address_line_4":{"description":"The fourth line of address","type":"string","x-nullable":true},"city":{"description":"The city the location is in","type":"string","x-nullable":true},"country":{"description":"The full country name, derived from the country code","type":"string","x-nullable":true},"country_code":{"default":"GB","description":"The two-letter code repesenting the country the location is in","enum":["AC","AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CP","CQ","CR","CU","CV","CW","CX","CY","CZ","DE","DG","DJ","DK","DM","DO","DZ","EA","EC","EE","EG","EH","ER","ES","ET","EU","EZ","FI","FJ","FK","FM","FO","FR","GA","GB","GD","GE","GF","GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","IC","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","QO","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TA","TC","TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG","UM","UN","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","XK","YE","YT","ZA","ZM","ZW"],"type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"latitude":{"description":"The latitude of the location in decimal degrees, derived from the postcode unless explicitly set","type":"number"},"longitude":{"description":"The longitude of the location in decimal degrees, derived from the postcode unless explicitly set","type":"number"},"name":{"description":"The name of the location","type":"string","x-nullable":true},"postcode":{"description":"The postcode of the location","type":"string"}},"title":"OrgUnitLocation","type":"object"},"OrgUnitLocationUpsertParams":{"description":"The params for creating or updating a location","example":{"address_line_1":"1 Bridge Street","city":"London","country_code":"GB","latitude":51.5007,"longitude":-0.1246,"name":"Head Office","postcode":"SW1A0AA"},"properties":{"address_line_1":{"description":"The first line of address","type":"string"},"address_line_2":{"description":"The second line of address","type":"string"},"address_line_3":{"description":"The third line of address","type":"string"},"address_line_4":{"description":"The fourth line of address","type":"string"},"city":{"description":"The city the location is in","type":"string"},"country_code":{"default":"GB","description":"The two-letter code repesenting the country the location is in","enum":["AC","AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CP","CQ","CR","CU","CV","CW","CX","CY","CZ","DE","DG","DJ","DK","DM","DO","DZ","EA","EC","EE","EG","EH","ER","ES","ET","EU","EZ","FI","FJ","FK","FM","FO","FR","GA","GB","GD","GE","GF","GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","IC","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","QO","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TA","TC","TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG","UM","UN","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","XK","YE","YT","ZA","ZM","ZW"],"type":"string"},"latitude":{"description":"The latitude of the location in decimal degrees, will be derived from the postcode unless explicitly set","type":"number"},"longitude":{"description":"The longitude of the location in decimal degrees, will be derived from the postcode unless explicitly set","type":"number"},"name":{"description":"The name of the location","type":"string"},"postcode":{"description":"The postcode of the location","type":"string"}},"required":["postcode","city","address_line_1"],"title":"OrgUnitLocationUpsertParams","type":"object"},"OrgUnitPublicHolidaysParams":{"description":"Public holidays IDs","example":{"public_holidays":["a1b2c3d4-e5f6-7890-abcd-ef1234567890","b2c3d4e5-f6a7-8901-bcde-f12345678901"]},"properties":{"public_holidays":{"items":{"type":"string"},"type":"array"}},"title":"Public holidays IDs","type":"object"},"OrgUnitRef":{"description":"A reference to an org unit by its Sona identifier","example":{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},"properties":{"id":{"description":"The unique Sona identifier of the org unit","type":"string"}},"required":["id"],"title":"OrgUnitRef","type":"object"},"OrgUnitType":{"description":"A single org unit type","example":{"id":"aa7a1bc2-50d2-4aa8-862b-a2e7483f1a15","name":"type 2","parent_org_unit_type":{"id":"c9d80598-8223-4836-875d-f6dd9552a77f"}},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the org unit type","type":"string"},"parent_org_unit_type":{"properties":{"id":{"description":"Sona unique identifier of the parent org unit type, or null for a top-level type","type":"string","x-nullable":true}},"type":"object"}},"required":["name","id"],"title":"OrgUnitType","type":"object"},"OrgUnitTypeCreateParams":{"description":"Org unit type params","example":{"name":"Department","parent_id":"c9d80598-8223-4836-875d-f6dd9552a77f"},"properties":{"name":{"description":"The name of the org unit type","type":"string"},"parent_id":{"description":"Sona unique identifier of the parent org unit type","type":"string"}},"required":["name"],"title":"Org unit type params","type":"object"},"OrgUnitTypes":{"description":"A collection of OrgUnitTypes","items":{"$ref":"#/definitions/OrgUnitType"},"title":"OrgUnitTypes","type":"array"},"OrgUnitUpdateParams":{"description":"Org unit params","example":{"accounting_cost_code":"OW-002","average_hourly_rate":15.75,"external_connections":[{"external_id":"EXT-OW-002"}],"is_base_entity":true,"name":"Oak Ward","org_unit_type_id":"c9d80598-8223-4836-875d-f6dd9552a77f","parent_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","paycaptain_company_id":"PC-5678"},"properties":{"accounting_cost_code":{"description":"Accounting cost code","type":"string"},"average_hourly_rate":{"description":"Average hourly rate","format":"float","type":"number"},"external_connections":{"description":"List of external connections for update. Existing external connections will be removed unless included in this list.","items":{"properties":{"external_id":{"description":"External identifier of the org unit","type":"string"}},"type":"object"},"type":"array"},"is_base_entity":{"description":"Whether it's a base entity","type":"boolean"},"name":{"description":"The name of the org unit","type":"string"},"org_unit_type_id":{"description":"Sona unique identifier of the org unit type","type":"string"},"parent_id":{"description":"Sona unique identifier of the parent org unit","type":"string","x-nullable":true},"paycaptain_company_id":{"description":"Pay captain company id","type":"string"}},"title":"Org unit params","type":"object"},"OrgUnits":{"description":"A collection of OrgUnits","items":{"$ref":"#/definitions/OrgUnit"},"title":"OrgUnits","type":"array"},"OrganisationCalendar":{"description":"Calendar settings of the organisation.","example":{"day_start_time":"04:00","fiscal_year_start":"04-01","week_start_day":"sunday"},"properties":{"day_start_time":{"description":"Time the organisation's business day starts, in 24-hour HH:MM form","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","type":"string"},"fiscal_year_start":{"description":"Month and day the organisation's fiscal year starts, as MM-DD (no year)","pattern":"^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$","type":"string"},"week_start_day":{"description":"Lowercase name of the day the organisation's week starts on","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"required":["fiscal_year_start","day_start_time","week_start_day"],"title":"Organisation Calendar","type":"object"},"PackageStatusCreated":{"properties":{"entries":{"description":"One per hour line","items":{"$ref":"#/definitions/HourStatusData"},"type":"array"}},"title":"Statuses added to every hour line","type":"object"},"PayGroup":{"description":"A single pay group","example":{"id":"29afd7cc-00b5-473a-83a1-5c8b59223fab","name":"Some Name","pay_frequency":"weekly","payroll_entity_name":"Brandon House"},"properties":{"id":{"description":"The unique identifier of the pay group","type":"string"},"name":{"description":"The name of the pay group","type":"string"},"pay_frequency":{"description":"The type of pay period","enum":["weekly","fortnightly","four_weekly","monthly","quarterly","bi_annual","annual","calendar_monthly"],"type":"string"},"payroll_entity_name":{"description":"The name of the payroll entity","type":"string"}},"title":"Pay group","type":"object"},"PayGroups":{"description":"A collection of pay groups","items":{"$ref":"#/definitions/PayGroup"},"title":"Pay groups","type":"array"},"PayLine":{"description":"A single pay line","example":{"comment":"payroll info for a single shift","duration_in_minutes":10,"end_datetime":"2024-06-18T10:37:48Z","entry_type":"regular","id":"e96de533-85c6-46d4-af7a-1f054783741e","pay_type":"some pay type","rate_of_pay":{"id":"e96de533-85c6-46d4-af7a-1f054783741e","pay_type":"hourly","rate":"£10.00"},"shift_id":"3f9723ec-f485-421b-9b47-8c62a3102716","shift_type_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","shift_type_name":"Day Shift","start_datetime":"2024-06-18T10:27:48Z","user_id":"12e9bab2-15f2-4c61-9421-cdb85a1631cb","variant":"Paid"},"properties":{"comment":{"description":"Pay line comment","type":"string"},"duration_in_minutes":{"description":"Duration in minutes","type":"integer"},"end_datetime":{"description":"The end datetime in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"entry_type":{"description":"Type of pay line","enum":["regular","amendment"],"type":"string"},"id":{"description":"Sona pay line unique identifier","type":"string"},"pay_type":{"description":"Pay type","type":"string"},"rate_of_pay":{"description":"The rate of pay the line was paid at, or null when there is none","properties":{"id":{"description":"Rate of pay id","type":"string"},"pay_type":{"description":"How it is paid, e.g. hourly / salaried","type":"string"},"rate":{"description":"The payment rate","type":"string"}},"type":"object","x-nullable":true},"shift_id":{"description":"Sona unique identifier of the shift","type":"string"},"shift_type_id":{"description":"Sona unique identifier of the shift type","type":"string"},"shift_type_name":{"description":"Name of the shift type","type":"string"},"start_datetime":{"description":"The start date time  in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_id":{"description":"The unique Sona identifier of the user entity","type":"string"},"variant":{"description":"The name of the line's pay variant","type":"string","x-nullable":true}},"title":"Pay lines","type":"object"},"PayLines":{"description":"A collection of pay lines","items":{"$ref":"#/definitions/PayLine"},"title":"Pay lines","type":"array"},"PayPeriod":{"description":"A single pay period within a pay group's schedule","example":{"cutoff_datetime":"2026-04-30T23:59:59Z","end_date":"2026-05-05","end_datetime":"2026-05-05T22:59:59Z","id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","is_parallel":false,"name":"April 2026","pay_datetime":"2026-05-25T00:00:00Z","pay_group_id":"29afd7cc-00b5-473a-83a1-5c8b59223fab","payslip_issue_datetime":"2026-05-22T00:00:00Z","period_number":1,"reference_period_end_date":"2026-04-20","reference_period_end_datetime":"2026-04-20T22:59:59Z","reference_period_start_date":"2026-03-21","reference_period_start_datetime":"2026-03-21T00:00:00Z","start_date":"2026-04-06","start_datetime":"2026-04-05T23:00:00Z","state":"open","tax_year":2026},"properties":{"cutoff_datetime":{"description":"The payroll cutoff datetime (ISO 8601)","type":"string"},"end_date":{"description":"The last day of the pay period, inclusive, in the organisation's timezone (YYYY-MM-DD)","type":"string"},"end_datetime":{"description":"The end datetime of the pay period (ISO 8601)","type":"string"},"id":{"description":"The unique identifier of the pay period","type":"string"},"is_parallel":{"description":"Whether this is a parallel-run period, not the live payroll","type":"boolean"},"name":{"description":"The name of the pay period","type":"string"},"pay_datetime":{"description":"The pay date (ISO 8601)","type":"string"},"pay_group_id":{"description":"The unique identifier of the pay group","type":"string"},"payslip_issue_datetime":{"description":"When payslips become available (ISO 8601)","type":"string"},"period_number":{"description":"The period number within the tax year","type":"integer"},"reference_period_end_date":{"description":"The last day of the reference period used for shift-based pay, inclusive (YYYY-MM-DD)","type":"string"},"reference_period_end_datetime":{"description":"The end datetime of the reference period (ISO 8601)","type":"string"},"reference_period_start_date":{"description":"The first day of the reference period used for shift-based pay, inclusive (YYYY-MM-DD)","type":"string"},"reference_period_start_datetime":{"description":"The start datetime of the reference period (ISO 8601)","type":"string"},"start_date":{"description":"The first day of the pay period, inclusive, in the organisation's timezone (YYYY-MM-DD)","type":"string"},"start_datetime":{"description":"The start datetime of the pay period (ISO 8601)","type":"string"},"state":{"description":"The state of the pay period (open or closed)","type":"string"},"tax_year":{"description":"The tax year this period belongs to","type":"integer"}},"title":"Pay period","type":"object"},"PayPeriods":{"description":"A collection of pay periods","items":{"$ref":"#/definitions/PayPeriod"},"title":"PayPeriods","type":"array"},"PayVariant":{"description":"A single pay variant","example":{"id":"e96de533-85c6-46d4-af7a-1f054783741e","name":"Unpaid time off","paid":false},"properties":{"id":{"description":"Sona pay variant unique identifier","type":"string"},"name":{"description":"The name of the variant","type":"string"},"paid":{"description":"Whether the variant is paid or not","type":"boolean"}},"title":"Pay variant","type":"object"},"PayVariants":{"description":"A collection of pay variants","items":{"$ref":"#/definitions/PayVariant"},"title":"Pay variants","type":"array"},"PayrollPaymentDeductionType":{"description":"A payroll payment or deduction type","example":{"additional_properties":{"is_regular_pay":true,"is_tip":false},"cr_code":"CR1","cr_cost_centre":"CC2","dr_code":"DR1","dr_cost_centre":"CC1","exclude_from_wage_cost":false,"external_type":"BasicPay","gender_pay_gap":"ordinary","holidayable":true,"id":"29afd7cc-00b5-473a-83a1-5c8b59223fab","min_wage":true,"name":"Basic Pay","niable":true,"pay_code":"PAY01","payslip_text":"Basic Pay","pensionable":true,"taxable":true,"type":"Payment"},"properties":{"additional_properties":{"description":"Additional properties map","type":"object"},"cr_code":{"description":"Credit cost code","type":"string","x-nullable":true},"cr_cost_centre":{"description":"Credit cost centre","type":"string","x-nullable":true},"dr_code":{"description":"Debit cost code","type":"string","x-nullable":true},"dr_cost_centre":{"description":"Debit cost centre","type":"string","x-nullable":true},"exclude_from_wage_cost":{"description":"Excluded from wage cost reporting","type":"boolean"},"external_type":{"description":"Category as used by external payroll system","type":"string","x-nullable":true},"gender_pay_gap":{"description":"Gender pay gap reporting category","type":"string"},"holidayable":{"description":"Included in holiday pay calculations","type":"boolean"},"id":{"description":"Unique Sona identifier","type":"string"},"min_wage":{"description":"Included in minimum wage calculations","type":"boolean","x-nullable":true},"name":{"description":"Descriptive name","type":"string","x-nullable":true},"niable":{"description":"Included in NI calculation","type":"boolean","x-nullable":true},"pay_code":{"description":"Unique pay code within the organisation","type":"string"},"payslip_text":{"description":"Text shown on employee payslip","type":"string","x-nullable":true},"pensionable":{"description":"Included in pension calculation","type":"boolean","x-nullable":true},"system_pay_code":{"description":"System-level pay code (final_holiday_pay, holiday_pay, ssp)","type":"string","x-nullable":true},"taxable":{"description":"Included in tax calculation","type":"boolean","x-nullable":true},"type":{"description":"Whether this is a payment, deduction or cost. Free text: `Payment`, `Deduction` and `Cost` are the conventional values, but records may hold others (e.g. `addition`).","type":"string"}},"required":["type","pay_code","id"],"title":"PayrollPaymentDeductionType","type":"object"},"PayrollPaymentDeductionTypeCreateParams":{"description":"Parameters for creating a payroll payment/deduction type","example":{"additional_properties":{"is_overtime":true,"is_regular_pay":false},"exclude_from_wage_cost":false,"external_type":"Overtime","gender_pay_gap":"ordinary","holidayable":true,"min_wage":false,"name":"Overtime 1.5x","niable":true,"pay_code":"OT150","payslip_text":"Overtime at time and a half","pensionable":false,"taxable":true,"type":"Payment"},"properties":{"additional_properties":{"description":"Additional properties map. Known keys: is_tip, is_regular_pay, is_hours, is_overtime, is_additional_payment (all booleans). TGP rules engine uses is_tip and is_regular_pay.","type":"object"},"cr_code":{"description":"Credit cost code","type":"string"},"cr_cost_centre":{"description":"Credit cost centre","type":"string"},"dr_code":{"description":"Debit cost code","type":"string"},"dr_cost_centre":{"description":"Debit cost centre","type":"string"},"exclude_from_wage_cost":{"default":false,"description":"Excluded from wage cost reporting","type":"boolean"},"external_type":{"description":"Category as used by external payroll system (must match Shape company pay elements). Required when Sona Pay sync is enabled.","type":"string"},"gender_pay_gap":{"description":"Gender pay gap reporting category","enum":["not_set","not_included","excluded","ordinary","bonus"],"type":"string"},"holidayable":{"default":true,"description":"Included in holiday pay calculations","type":"boolean"},"min_wage":{"description":"Included in minimum wage calculations","type":"boolean"},"name":{"description":"Descriptive name","type":"string"},"niable":{"description":"Included in NI calculation","type":"boolean"},"pay_code":{"description":"Unique pay code within the organisation","type":"string"},"payslip_text":{"description":"Text shown on employee payslip","type":"string"},"pensionable":{"description":"Included in pension calculation","type":"boolean"},"system_pay_code":{"description":"System-level pay code","enum":["final_holiday_pay","holiday_pay","ssp"],"type":"string"},"taxable":{"description":"Included in tax calculation","type":"boolean"},"type":{"description":"Whether this is a payment, deduction or cost. Free text: the API stores any string. `Payment`, `Deduction` and `Cost` are the conventional values, but existing records may hold others (e.g. `addition`).","type":"string"}},"required":["type","pay_code"],"title":"PayrollPaymentDeductionType create params","type":"object"},"PayrollPaymentDeductionTypeUpdateParams":{"description":"Parameters for updating a payroll payment/deduction type. additional_properties uses merge semantics.","example":{"additional_properties":{"is_overtime":true},"name":"Overtime 2x","payslip_text":"Overtime at double time","taxable":true},"properties":{"additional_properties":{"description":"Additional properties map (merge semantics - existing keys preserved). Known keys: is_tip, is_regular_pay, is_hours, is_overtime, is_additional_payment (all booleans).","type":"object"},"cr_code":{"description":"Credit cost code","type":"string"},"cr_cost_centre":{"description":"Credit cost centre","type":"string"},"dr_code":{"description":"Debit cost code","type":"string"},"dr_cost_centre":{"description":"Debit cost centre","type":"string"},"exclude_from_wage_cost":{"description":"Excluded from wage cost reporting","type":"boolean"},"external_type":{"description":"Category as used by external payroll system (must match Shape company pay elements)","type":"string"},"gender_pay_gap":{"description":"Gender pay gap reporting category","enum":["not_set","not_included","excluded","ordinary","bonus"],"type":"string"},"holidayable":{"description":"Included in holiday pay calculations","type":"boolean"},"min_wage":{"description":"Included in minimum wage calculations","type":"boolean"},"name":{"description":"Descriptive name","type":"string"},"niable":{"description":"Included in NI calculation","type":"boolean"},"pay_code":{"description":"Unique pay code within the organisation","type":"string"},"payslip_text":{"description":"Text shown on employee payslip","type":"string"},"pensionable":{"description":"Included in pension calculation","type":"boolean"},"system_pay_code":{"description":"System-level pay code","enum":["final_holiday_pay","holiday_pay","ssp"],"type":"string"},"taxable":{"description":"Included in tax calculation","type":"boolean"},"type":{"description":"Whether this is a payment, deduction or cost. Free text: the API stores any string. `Payment`, `Deduction` and `Cost` are the conventional values, but existing records may hold others (e.g. `addition`).","type":"string"}},"title":"PayrollPaymentDeductionType update params","type":"object"},"PayrollPaymentDeductionTypes":{"description":"A collection of payroll payment/deduction types","items":{"$ref":"#/definitions/PayrollPaymentDeductionType"},"title":"PayrollPaymentDeductionTypes","type":"array"},"PayrollRunRow":{"description":"A raw payroll run row associated with a shift","example":{"duration_in_minutes":480,"end_datetime":"2024-06-15T16:00:00Z","entry_type":"normal","id":"7a3c9e12-bf45-4d01-9e8a-2c6d4f8b0a13","pay_type":"basic","start_datetime":"2024-06-15T08:00:00Z","variant":"Sickness"},"properties":{"duration_in_minutes":{"description":"Duration in minutes","type":"integer"},"end_datetime":{"description":"Payroll row end datetime","type":"string"},"entry_type":{"description":"Entry type","type":"string"},"id":{"description":"Payroll run row id","type":"string"},"pay_type":{"description":"Pay type","type":"string"},"start_datetime":{"description":"Payroll row start datetime","type":"string"},"variant":{"description":"Pay variant","type":"string"}},"required":["end_datetime","start_datetime","id"],"title":"Payroll Run Row","type":"object"},"PayrollSubmissionStatus":{"description":"The payroll submission and reconciliation status of a single worked shift","example":{"exceptions":["pay_query"],"finish_datetime":"2026-01-20T17:00:00Z","internal_id":"ROTA-100294","org_unit":{"id":"a1b2c3d4-e5f6-4a1b-8c2d-3e4f5a6b7c8d","name":"Sunrise Care Home"},"reconciliation_status":"needs_attention","shift_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","shift_type":{"id":"f0e1d2c3-b4a5-4968-8776-5a4b3c2d1e0f","name":"Nurse"},"start_datetime":"2026-01-20T09:00:00Z","submission_status":"pending","user":{"id":"2b7c1f0e-2d3a-4b8b-9c1d-6f5e4a3b2c1d"}},"properties":{"approved_for_payroll_at":{"description":"When the shift was approved (submitted) for payroll; null when still pending","format":"date-time","type":"string","x-nullable":true},"exceptions":{"description":"Reasons the shift needs attention (empty when resolved)","items":{"enum":["pay_query","missing_pay_variant","shift_in_progress","missing_pay_record"],"type":"string"},"type":"array"},"finish_datetime":{"description":"Shift finish in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"internal_id":{"description":"Your own external identifier for the shift, if one was set","type":"string","x-nullable":true},"org_unit":{"description":"The org unit (location) of the shift","type":"object"},"reconciliation_status":{"description":"Whether the shift needs attention before it can be submitted","enum":["resolved","needs_attention"],"type":"string"},"shift_id":{"description":"Sona unique identifier of the shift","type":"string"},"shift_type":{"description":"The shift type (role) of the shift","type":"object"},"start_datetime":{"description":"Shift start in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"submission_status":{"description":"Submission status","enum":["pending","submitted"],"type":"string"},"user":{"description":"The employee who worked the shift","type":"object"}},"required":["reconciliation_status","submission_status","shift_type","org_unit","user","shift_id"],"title":"Payroll Submission Status","type":"object"},"PayrollSubmissionStatuses":{"description":"A collection of payroll submission statuses","items":{"$ref":"#/definitions/PayrollSubmissionStatus"},"title":"Payroll Submission Statuses","type":"array"},"PeopleWeSupportBudget":{"description":"A budget for a person we support","example":{"end_date":"2024-08-01","hours":10.0,"id":"47dab27a-e9dd-4a4a-a75b-d91df22315b4","people_we_support_id":"edfb82b8-0d90-4943-b4df-d27a357c03b5","start_date":"2024-07-31","type_of_work_id":"b2b09422-c909-4198-a915-f66af3be2ec9"},"properties":{"end_date":{"description":"End date","format":"date","type":"string"},"hours":{"description":"Hours (instances for sleep)","format":"float","type":"number"},"id":{"description":"The unique identifier of PeopleWeSupport budget entry","type":"string"},"people_we_support_id":{"description":"The unique identifier of person we support","type":"string"},"start_date":{"description":"Start date","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type of care","type":"string"}},"title":"People we support budget","type":"object"},"PeopleWeSupportBudgets":{"description":"The budgets that were created or updated","example":[{"end_date":"2024-08-01","hours":10.0,"id":"47dab27a-e9dd-4a4a-a75b-d91df22315b4","people_we_support_id":"edfb82b8-0d90-4943-b4df-d27a357c03b5","start_date":"2024-07-31","type_of_work_id":"b2b09422-c909-4198-a915-f66af3be2ec9"}],"items":{"$ref":"#/definitions/PeopleWeSupportBudget"},"title":"People we support budgets data","type":"array"},"PeopleWeSupportBudgetsList":{"description":"A collection of people we support budget records","example":[{"end_date":"2024-08-01","hours":10.0,"id":"47dab27a-e9dd-4a4a-a75b-d91df22315b4","people_we_support_id":"edfb82b8-0d90-4943-b4df-d27a357c03b5","start_date":"2024-07-31","type_of_work_id":"b2b09422-c909-4198-a915-f66af3be2ec9"}],"items":{"$ref":"#/definitions/PeopleWeSupportBudget"},"title":"People we support budgets","type":"array"},"PeopleWeSupportCreateBudgetParams":{"description":"People we support create budget params","example":{"end_date":"2024-08-01","hours":10.0,"start_date":"2024-07-31","type_of_work_id":"b2b09422-c909-4198-a915-f66af3be2ec9"},"properties":{"end_date":{"description":"End date","format":"date","type":"string"},"hours":{"description":"Hours (instances for sleep)","format":"float","type":"number"},"start_date":{"description":"Start date","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type of care","type":"string"}},"required":["start_date","type_of_work_id","hours"],"title":"People we support create budget params","type":"object"},"PeopleWeSupportCreateParams":{"description":"People we support create params","example":{"end_date":"2025-01-15","integration_idx":"PWS-EXT-001","location":{"address_line_1":"42 Elm Street","address_line_2":"Westbury","city":"Bristol","country":"United Kingdom","name":"Rosewood House","postcode":"BS1 4DJ"},"name":"Margaret Thompson","nhs_number":"943 476 5919","org_unit_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","start_date":"2024-01-15"},"properties":{"end_date":{"description":"Start date in ISO 8601 standard","type":"string"},"integration_idx":{"description":"Integration idx","type":"string"},"location":{"properties":{"address_line_1":{"description":"Address line 1","type":"string"},"address_line_2":{"description":"Address line 2","type":"string"},"address_line_3":{"description":"Address line 3","type":"string"},"address_line_4":{"description":"Address line 4","type":"string"},"city":{"description":"City","type":"string"},"country":{"description":"Country name","type":"string"},"name":{"description":"Location name","type":"string"},"postcode":{"description":"Postal code","type":"string"}},"type":"object"},"name":{"description":"Person full name","type":"string"},"nhs_number":{"description":"NHS number","type":"string"},"org_unit_id":{"description":"The unique Sona identifier of the org unit","type":"string"},"start_date":{"description":"Start date in ISO 8601 standard","type":"string"},"upload_id":{"description":"External id (`idx`), unique within your organisation","type":"string"}},"required":["name","org_unit_id"],"title":"People we support create params","type":"object"},"PeopleWeSupportData":{"description":"People we support data","example":{"end_date":"2025-01-15","id":"f8b6d2e7-9c0a-4345-a67b-8d9e01234fa1","integration_idx":"PWS-EXT-001","location":{"address_line_1":"42 Elm Street","address_line_2":"Westbury","city":"Bristol","country":"United Kingdom","id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Rosewood House","postcode":"BS1 4DJ"},"name":"Margaret Thompson","nhs_number":"943 476 5919","org_unit_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","start_date":"2024-01-15"},"properties":{"current_care_package":{"description":"The care package in force today (UTC date): started, and not yet ended. Null when there is none.","properties":{"id":{"description":"The unique Sona identifier of the care package","type":"string"},"idx":{"description":"Package code","type":"string","x-nullable":true},"name":{"description":"Package name","type":"string","x-nullable":true},"start_date":{"description":"Start date in ISO 8601 standard","type":"string"}},"type":"object","x-nullable":true},"end_date":{"description":"Start date in ISO 8601 standard","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the person we support","type":"string"},"integration_idx":{"description":"Integration idx","type":"string","x-nullable":true},"location":{"properties":{"address_line_1":{"description":"Address line 1","type":"string","x-nullable":true},"address_line_2":{"description":"Address line 2","type":"string","x-nullable":true},"address_line_3":{"description":"Address line 3","type":"string","x-nullable":true},"address_line_4":{"description":"Address line 4","type":"string","x-nullable":true},"city":{"description":"City","type":"string","x-nullable":true},"country":{"description":"Country name","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the location","type":"string"},"name":{"description":"Location name","type":"string","x-nullable":true},"postcode":{"description":"Postal code","type":"string","x-nullable":true}},"type":"object","x-nullable":true},"name":{"description":"Person full name","type":"string"},"nhs_number":{"description":"NHS number","type":"string","x-nullable":true},"org_unit_id":{"description":"The unique Sona identifier of the org unit","type":"string"},"start_date":{"description":"Start date in ISO 8601 standard","type":"string","x-nullable":true},"status":{"description":"Current status, `active` or `inactive`. Null when no status record exists (shown as Unknown in the admin screen), which is the case for anyone never logged out of service. The `status=active` filter counts them as active.","type":"string","x-nullable":true},"updated_at_utc":{"description":"Last update of the person record, UTC ISO 8601","type":"string"},"upload_id":{"description":"External id (`idx`)","type":"string","x-nullable":true}},"title":"People we support data","type":"object"},"PeopleWeSupportDataList":{"description":"A collection of people we support records","items":{"$ref":"#/definitions/PeopleWeSupportData"},"title":"People we support","type":"array"},"PeopleWeSupportUpdateBudgetParams":{"description":"People we support update budget params","example":{"end_date":"2024-08-01","hours":10.0,"start_date":"2024-07-31"},"properties":{"end_date":{"description":"End date","format":"date","type":"string"},"hours":{"description":"Hours (instances for sleep)","format":"float","type":"number"},"start_date":{"description":"Start date","format":"date","type":"string"}},"required":["start_date","hours"],"title":"People we support update budget params","type":"object"},"PeopleWeSupportUpdateParams":{"description":"People we support update params","example":{"end_date":"2025-06-30","integration_idx":"PWS-EXT-001","location":{"address_line_1":"42 Elm Street","address_line_2":"Westbury","city":"Bristol","country":"United Kingdom","name":"Rosewood House","postcode":"BS1 4DJ"},"name":"Margaret Thompson","nhs_number":"943 476 5919","start_date":"2024-01-15"},"properties":{"end_date":{"description":"Start date in ISO 8601 standard","type":"string"},"integration_idx":{"description":"Integration idx","type":"string"},"location":{"properties":{"address_line_1":{"description":"Address line 1","type":"string"},"address_line_2":{"description":"Address line 2","type":"string"},"address_line_3":{"description":"Address line 3","type":"string"},"address_line_4":{"description":"Address line 4","type":"string"},"city":{"description":"City","type":"string"},"country":{"description":"Country name","type":"string"},"name":{"description":"Location name","type":"string"},"postcode":{"description":"Postal code","type":"string"}},"type":"object"},"name":{"description":"Person full name","type":"string"},"nhs_number":{"description":"NHS number","type":"string"},"start_date":{"description":"Start date in ISO 8601 standard","type":"string"},"upload_id":{"description":"External id (`idx`), unique within your organisation","type":"string"}},"title":"People we support update params","type":"object"},"PermissionRole":{"description":"A role that defines a set of permissions for users","example":{"description":"Manages ward-level scheduling and staff assignments","id":"c4f7a312-8e1b-4d6a-9f3c-2a5b8e0d1f4a","location_specific":true,"name":"Ward Manager"},"properties":{"description":{"description":"Description of the role","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"location_specific":{"description":"Is the role location specific?","type":"boolean"},"name":{"description":"Name of the role","type":"string"}},"required":["description","name","id"],"title":"Permission Role","type":"object"},"PermissionRoles":{"description":"A collection of permission roles","items":{"$ref":"#/definitions/PermissionRole"},"title":"Permission Roles","type":"array"},"PermissionRolesLocations":{"description":"mapping of location specific permission roles to org units","items":{"properties":{"org_unit_ids":{"description":"The unique Sona identifiers of the org units","items":{"description":"The unique Sona identifier of the org unit","type":"string"},"type":"array"},"permission_role_id":{"description":"The unique Sona identifier of the permission role","type":"string"}},"type":"object"},"title":"Permission Roles Locations","type":"array"},"ProductivityCalculateRequest":{"description":"Parameters for a productivity calculation.","example":{"from_utc":"2024-06-15T06:00:00Z","interval_minutes":30,"roles":[{"id":"d3e4f5a6-7890-4abc-bcde-444444444444","name":"Registered Nurse"}],"rounding":{"mode":"threshold","threshold":0.5},"shifts":[{"end_datetime_utc":"2024-06-15T15:00:00Z","role_id":"d3e4f5a6-7890-4abc-bcde-444444444444","start_datetime_utc":"2024-06-15T07:00:00Z"}],"timezone":"Europe/London","to_utc":"2024-06-15T22:00:00Z"},"properties":{"from_utc":{"description":"Start of calculation window (ISO8601 UTC)","type":"string"},"interval_minutes":{"description":"Time interval granularity in minutes (default: 30)","type":"integer"},"metrics":{"description":"Metric data with id, name, and transactions (optional)","items":{"type":"object"},"type":"array"},"roles":{"description":"Roles with id and name","items":{"type":"object"},"type":"array"},"rounding":{"description":"Rounding configuration with mode and optional threshold","type":"object"},"rules":{"description":"Map of rule type to array of rules (7 rule types supported)","type":"object"},"shifts":{"description":"Shifts with role_id, start_datetime_utc, end_datetime_utc","items":{"type":"object"},"type":"array"},"strategy_id":{"description":"Optional UUID of a labour strategy. When set, demand is calculated from the strategy's group assignments for the org unit (status-agnostic — draft, live, and archived strategies are all supported). Mutually exclusive with `rules`.","type":"string"},"timezone":{"description":"Timezone for interpreting rule local times (default: Etc/UTC)","type":"string"},"to_utc":{"description":"End of calculation window (ISO8601 UTC)","type":"string"}},"required":["to_utc","from_utc"],"title":"Productivity Calculate Request","type":"object"},"ProductivityCalculateResponse":{"description":"The result of a productivity calculation.","example":{"data":{"calculation_result":"ok","role_demand_list":[{"intervals":[{"end_utc":"2024-06-15T07:30:00Z","required_workers":3.0,"scheduled_workers":2,"start_utc":"2024-06-15T07:00:00Z"}],"role_id":"d3e4f5a6-7890-4abc-bcde-444444444444","role_name":"Registered Nurse"}]}},"properties":{"data":{"description":"Calculation results containing role_demand_list and calculation_result","type":"object"}},"required":["data"],"title":"Productivity Calculate Response","type":"object"},"ProductivityConfig":{"description":"The productivity configuration for an org unit.","example":{"org_unit_id":"550e8400-e29b-41d4-a716-446655440000","rounding_mode":"threshold","rounding_threshold":0.5,"rule_sets":[{"id":"550e8400-e29b-41d4-a716-446655440001","name":"Default Rules","rules":[{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":20},"rule_type":"inflexible_demand_linear"}]}]},"properties":{"org_unit_id":{"description":"UUID of the org unit","format":"uuid","type":"string"},"rounding_mode":{"description":"Rounding mode for required workers","enum":["none","ceil","floor","threshold"],"type":"string"},"rounding_threshold":{"description":"Threshold value for rounding (0-1)","type":"number"},"rule_sets":{"description":"List of rule sets","items":{"$ref":"#/definitions/ProductivityRuleSet"},"type":"array"}},"required":["rule_sets","rounding_threshold","rounding_mode","org_unit_id"],"title":"Productivity Config","type":"object"},"ProductivityConfigResponse":{"description":"The productivity configuration response wrapper.","example":{"productivity_config":{"org_unit_id":"550e8400-e29b-41d4-a716-446655440000","rounding_mode":"threshold","rounding_threshold":0.5,"rule_sets":[{"id":"550e8400-e29b-41d4-a716-446655440001","name":"Default Rules","rules":[{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":20},"rule_type":"inflexible_demand_linear"}]}]}},"properties":{"productivity_config":{"$ref":"#/definitions/ProductivityConfig","description":"The productivity config or null","x-nullable":true}},"title":"Productivity Config Response","type":"object"},"ProductivityRule":{"description":"A single productivity rule.","example":{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":20},"rule_type":"inflexible_demand_linear"},"properties":{"master_shift_type_id":{"description":"UUID of the master shift type","format":"uuid","type":"string"},"metric_id":{"description":"UUID of the metric","format":"uuid","type":"string","x-nullable":true},"rule_params":{"description":"Rule-specific parameters","type":"object"},"rule_type":{"description":"Type of the rule","enum":["inflexible_demand_linear","inflexible_demand_specified","inflexible_cumulative_demand_fixed_tasks","flexible_cumulative_demand_fixed_tasks","inflexible_fixed_tasks","inflexible_cumulative_demand_minimum_staffing","inflexible_minimum_staffing","inflexible_maximum_staffing","flexible_fixed_tasks"],"type":"string"}},"required":["rule_params","master_shift_type_id","rule_type"],"title":"Productivity Rule","type":"object"},"ProductivityRuleSet":{"description":"A set of productivity rules.","example":{"id":"550e8400-e29b-41d4-a716-446655440001","name":"Weekday Demand Rules","rules":[{"master_shift_type_id":"550e8400-e29b-41d4-a716-446655440002","metric_id":"550e8400-e29b-41d4-a716-446655440003","rule_params":{"ratio":20},"rule_type":"inflexible_demand_linear"}]},"properties":{"id":{"description":"UUID of the rule set","format":"uuid","type":"string"},"name":{"description":"Name of the rule set","type":"string"},"rules":{"description":"List of rules in this set","items":{"$ref":"#/definitions/ProductivityRule"},"type":"array"}},"required":["rules","name","id"],"title":"Productivity Rule Set","type":"object"},"PublicHoliday":{"description":"Public holidays data","example":{"country_code":"GB","date":"2024-06-03","day_of_week":"Monday","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","is_substitute":true,"name":"Some Random Holiday","regions":["England","Scotland"]},"properties":{"country_code":{"description":"Country code","type":"string"},"date":{"description":"Holiday date","format":"date","type":"string"},"day_of_week":{"description":"Day of week","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"is_substitute":{"description":"Substitute?","type":"boolean"},"name":{"description":"Public holiday name","type":"string"},"regions":{"description":"Country regions","items":{"type":"string"},"type":"array"}},"title":"Public holidays data","type":"object"},"PublicHolidays":{"description":"Public holidays data","items":{"$ref":"#/definitions/PublicHoliday"},"title":"Public holidays data","type":"array"},"RateOfPay":{"description":"A rate of pay","example":{"employment_period_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","employment_period_idx":"159600fb-3fa9-4687-bbc9-196375de6ee3","finish_date":"2020-04-20","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","idx":"external id","is_default":false,"label":"Standard Rate","org_unit_id":"c65bf5ec-5e82-4207-90cd-29caca7eae75","organisation_user_id":"0b6d8f0e-6a3c-4a51-9a52-2f1d7e3c9b11","pay_type":"hourly","rate":"£17.50","start_date":"2019-04-20","type_of_work_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","user_id":"5c7265ad-440a-4d20-9b93-bfc287ee0909"},"properties":{"employment_period_id":{"description":"The unique Sona identifier of the employment period","type":"string"},"employment_period_idx":{"description":"External identifier of the employment_period","type":"string","x-nullable":true},"finish_date":{"description":"The date from when this rate of pay is no longer applicable. Expects YYYY-MM-DD format","format":"date","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the rate of pay","type":"string"},"idx":{"description":"An external identifier of the rate of pay.","type":"string","x-nullable":true},"is_default":{"description":"Whether this is a default rate of pay","type":"boolean"},"label":{"description":"A label for the rate of pay","type":"string","x-nullable":true},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string","x-nullable":true},"organisation_user_id":{"description":"Sona unique identifier of the user in the organisation","type":"string"},"pay_type":{"description":"How it is paid, e.g. hourly / salaried","enum":["hourly","salaried"],"type":"string"},"rate":{"description":"The payment rate","type":"string"},"start_date":{"description":"The date from when this rate of pay is applicable. Expects YYYY-MM-DD format","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type_of_work","type":"string","x-nullable":true},"user_id":{"description":"Sona unique identifier of the user for this rate of pay","type":"string"}},"required":["org_unit_id","type_of_work_id","organisation_user_id","user_id","start_date","rate","id"],"title":"Rate of Pay","type":"object"},"RateOfPayCreateParams":{"description":"A rate of pay","example":{"amount":1750,"currency":"GBP","employment_period_id":"159600fb-3fa9-4687-bbc9-196375de6ee3","employment_period_idx":"159600fb-3fa9-4687-bbc9-196375de6ee3","finish_date":"2020-04-20","idx":"external id","is_default":false,"label":"Standard Rate","org_unit_id":"c65bf5ec-5e82-4207-90cd-29caca7eae75","pay_type":"hourly","start_date":"2019-04-20","type_of_work_id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},"properties":{"amount":{"description":"Amount represented in lowest value in given currency, pennies/cents (e.g. amount: 1241 represents 12.41, 500 represents 5.00)","type":"integer"},"currency":{"default":"GBP","description":"The currency used","type":"string"},"employment_period_id":{"description":"The unique Sona identifier of the employment period. At least one of employment_period_id or employment_period_idx is required.","type":"string"},"employment_period_idx":{"description":"External identifier of the employment_period. At least one of employment_period_id or employment_period_idx is required.","type":"string"},"finish_date":{"description":"The date from when this rate of pay is no longer applicable. Expects YYYY-MM-DD format","format":"date","type":"string"},"idx":{"description":"An external identifier of the rate of pay.","type":"string"},"is_default":{"description":"Whether this is a default rate of pay","type":"boolean"},"label":{"description":"A label for the rate of pay","type":"string"},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string"},"pay_type":{"default":"hourly","description":"How it is paid, e.g. hourly / salaried","enum":["hourly","salaried"],"type":"string"},"start_date":{"description":"The date from when this rate of pay is applicable. Expects YYYY-MM-DD format","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type_of_work","type":"string"}},"required":["pay_type","amount","start_date"],"title":"Rate of Pay params","type":"object"},"RateOfPayParams":{"description":"A rate of pay to attach to the employment spec. Supports two modes:\n\n1. **Reference an existing rate**: provide `id` to link a pre-existing rate of pay.\n2. **Create a new rate inline**: omit `id` and provide `amount`, `currency`, `start_date`, `pay_type` (and optionally `finish_date`, `idx`, `is_default`, `label`, `type_of_work_id`, `org_unit_id`) to create a new rate of pay as part of the employment period creation.\n\nWhen both `id` and inline fields are provided, `id` takes precedence and the inline fields are silently ignored.","example":{"amount":1250,"currency":"GBP","finish_date":"2025-03-01","is_default":false,"pay_type":"hourly","start_date":"2024-03-01"},"properties":{"amount":{"description":"Amount represented in lowest value in given currency, pennies/cents (e.g. amount: 1241 represents 12.41, 500 represents 5.00)","type":"integer"},"currency":{"default":"GBP","description":"The currency used","type":"string"},"finish_date":{"description":"The date from when this rate of pay is no longer applicable","format":"date","type":"string"},"id":{"description":"The unique Sona identifier of an existing rate of pay. Required when referencing an existing rate; omit when creating a new rate inline.","type":"string"},"idx":{"description":"An external identifier of the rate of pay.","type":"string"},"is_default":{"description":"Whether this is a default rate of pay","type":"boolean"},"label":{"description":"A label for the rate of pay","type":"string"},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string"},"pay_type":{"default":"hourly","description":"How it is paid, e.g. hourly / salaried","enum":["hourly","salaried"],"type":"string"},"start_date":{"description":"The date from when this rate of pay is applicable","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type_of_work","type":"string"}},"title":"Rate of Pay params","type":"object"},"RateOfPayUpdateParams":{"description":"Parameters for updating a rate of pay. A rate of pay defines the monetary compensation for a specific user, type of work, and organisational unit over a date range. Rates can be hourly or salaried.","example":{"amount":1750,"currency":"GBP","finish_date":"2020-04-20","idx":"new external id","is_default":false,"is_idx":true,"label":"Standard Rate","org_unit_id":"c65bf5ec-5e82-4207-90cd-29caca7eae75","pay_type":"hourly","start_date":"2019-04-20","type_of_work_id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},"properties":{"amount":{"description":"Amount represented in lowest value in given currency, pennies/cents (e.g. amount: 1241 represents 12.41, 500 represents 5.00)","type":"integer"},"currency":{"default":"GBP","description":"The currency used","type":"string"},"finish_date":{"description":"The date from when this rate of pay is no longer applicable. Expects YYYY-MM-DD format","format":"date","type":"string"},"idx":{"description":"A new external identifier for the rate of pay. Replaces the current idx; use the new value for later lookups by idx.","type":"string"},"is_default":{"description":"Whether this is a default rate of pay","type":"boolean"},"is_idx":{"description":"If rate_of_pay_id_or_idx parameter is an external id this parameter should be set to true","type":"boolean"},"label":{"description":"A label for the rate of pay","type":"string"},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string"},"pay_type":{"default":"hourly","description":"How it is paid, e.g. hourly / salaried","enum":["hourly","salaried"],"type":"string"},"start_date":{"description":"The date from when this rate of pay is applicable. Expects YYYY-MM-DD format","format":"date","type":"string"},"type_of_work_id":{"description":"The unique Sona identifier of the type_of_work","type":"string"}},"title":"Rate of Pay params","type":"object"},"RatesOfPay":{"description":"A collection of RatesOfPay","items":{"$ref":"#/definitions/RateOfPay"},"title":"RatesOfPay","type":"array"},"RetractFromUserParameters":{"description":"Parameters for removing an attribute or a skill","example":{"user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"user_id":{"description":"The identifier of the user the skill or attribute should be removed from","type":"string"}},"required":["user_id"],"title":"Retract skill or attribute from the user","type":"object"},"RightToWorkDocument":{"description":"A single right to work document entry","example":{"cos_field_1":"COS Value 1","cos_field_2":"COS Value 2","country_of_issue":"HN","date_checked":"2023-06-15","document_type":"passport","expiry_date":"2025-12-31","id":"550e8400-e29b-41d4-a716-446655440000","issue_date":"2020-01-01","nationality":"HN","notes":"Some additional notes","share_code":"abc123456"},"properties":{"cos_field_1":{"description":"COS field 1","type":"string","x-nullable":true},"cos_field_2":{"description":"COS field 2","type":"string","x-nullable":true},"country_of_issue":{"description":"ISO 3166 Alpha-2 code for the country of issue of the document","type":"string"},"date_checked":{"description":"The date when document was checked","format":"date","type":"string"},"document_type":{"description":"The type of the document","enum":["passport","visa","dbs","driving_license","eu_settlement","ilr","sia","share_code","student_visa","work_permit","external_id_check","skilled_worker_visa"],"type":"string"},"expiry_date":{"description":"The expiration date of the document","format":"date","type":"string","x-nullable":true},"id":{"description":"The unique identifier of the right to work document entry","type":"string"},"issue_date":{"description":"The issue date of the document","format":"date","type":"string","x-nullable":true},"nationality":{"description":"The nationality in the document","type":"string"},"notes":{"description":"Additional notes about document","type":"string","x-nullable":true},"share_code":{"description":"Share code","type":"string"}},"title":"Right to work document","type":"object"},"RightToWorkDocuments":{"description":"A collection of right to work documents","items":{"$ref":"#/definitions/RightToWorkDocument"},"title":"Right to work documents","type":"array"},"ServiceAuditEntry":{"example":{"actioned_by_source":"api","field_label":"service_capacity","id":"0b9e6f4a-3c2d-4e1f-8a7b-6c5d4e3f2a1b","inserted_at_utc":"2026-10-12T09:00:00Z","new_value":"12","previous_value":"10"},"properties":{"actioned_by_source":{"description":"`api`, `user`, `csv_import` and so on","type":"string"},"author_id":{"description":"The user, when a user made the change","type":"string","x-nullable":true},"field_label":{"description":"What changed","type":"string"},"id":{"description":"Entry id","type":"string"},"inserted_at_utc":{"description":"UTC ISO 8601","type":"string"},"new_value":{"description":"New value","type":"string","x-nullable":true},"previous_value":{"description":"Previous value","type":"string","x-nullable":true}},"title":"Service audit entry","type":"object"},"ServiceAuditLogList":{"items":{"$ref":"#/definitions/ServiceAuditEntry"},"title":"Service audit log","type":"array"},"ServiceConfigData":{"properties":{"end_date":{"description":"ISO 8601 date","type":"string","x-nullable":true},"id":{"description":"Config id","type":"string"},"inactive_reasons":{"description":"Stored out-of-service reasons. The admin screen falls back to a default list when none is set.","items":{"type":"string"},"type":"array","x-nullable":true},"max_assignment_capacity":{"description":"Maximum people supported","type":"integer","x-nullable":true},"non_contact_label":{"description":"Non-contact label","type":"string","x-nullable":true},"start_date":{"description":"ISO 8601 date","type":"string"},"target_assignment_count":{"description":"Target people supported","type":"integer","x-nullable":true},"target_staff_count":{"description":"Target staff count","type":"integer","x-nullable":true},"updated_at_utc":{"description":"UTC ISO 8601","type":"string"}},"title":"Service config data","type":"object"},"ServiceConfigParams":{"description":"Send only the fields you want to set. `start_date` is required on create.","example":{"inactive_reasons":["Hospital admission","Respite"],"max_assignment_capacity":12,"start_date":"2026-01-01"},"properties":{"end_date":{"description":"ISO 8601 date, not before start_date. Ends the config.","type":"string","x-nullable":true},"inactive_reasons":{"description":"Out-of-service reasons. Blank entries are ignored.","items":{"type":"string"},"type":"array","x-nullable":true},"max_assignment_capacity":{"description":"Maximum people supported, greater than 0","type":"integer","x-nullable":true},"non_contact_label":{"description":"Label for non-contact time","type":"string","x-nullable":true},"start_date":{"description":"ISO 8601 date. Required when creating.","type":"string"},"target_assignment_count":{"description":"Target people supported count, greater than 0","type":"integer","x-nullable":true},"target_staff_count":{"description":"Target staff count, greater than 0","type":"integer","x-nullable":true}},"title":"Service config params","type":"object"},"ServiceData":{"description":"A service with its demand-based rostering setup","example":{"config":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","inactive_reasons":["Hospital admission","Respite"],"max_assignment_capacity":12,"start_date":"2026-01-01","updated_at_utc":"2026-10-12T09:00:00Z"},"configured":true,"demand_periods":[{"end_time":"19:00","id":"7a1f0c9e-2b3d-4c5e-8f60-1a2b3c4d5e6f","name":"Day","position":0,"start_time":"07:00","updated_at_utc":"2026-10-12T09:00:00Z"}],"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","idx":"SVC001","name":"Rosewood House"},"properties":{"config":{"$ref":"#/definitions/ServiceConfigData","x-nullable":true},"configured":{"description":"Whether a config is in force today","type":"boolean"},"demand_periods":{"description":"Demand periods in order","items":{"$ref":"#/definitions/DemandPeriodData"},"type":"array"},"id":{"description":"The Sona identifier of the service (org unit)","type":"string"},"idx":{"description":"The external id (the org unit `upload_id`)","type":"string","x-nullable":true},"name":{"description":"Service name","type":"string"}},"title":"Service data","type":"object"},"ServiceDataList":{"items":{"$ref":"#/definitions/ServiceData"},"title":"Services","type":"array"},"Shift":{"description":"A single shift: a rostered block of time with type, location, and optional worker. The `filling_way` field describes how an assigned worker was placed onto the shift (open-shift booking versus manager assignment); see that property for semantics when integrating with payroll or drawdown products.","example":{"agency":{"id":"fdd96a57-6f7d-43df-be39-73a4f5af0709"},"category":"open_shift","description":"Description of the shift","draft_of_shift":{"id":"4139288a-1dc8-45f8-b39e-f9c4700b812b"},"employment_period":{"id":"3b1e8f02-6c4d-4f7a-9d51-2a8c0e7b4f19"},"filling_way":"assigned","finish_datetime":"2019-04-20T15:47:58Z","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","internal_id":"RGM15RMC","is_shift_run":false,"is_travel_gap":false,"org_unit":{"id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"people_we_support":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","integration_idx":"PWS-001","name":"Jane Smith"}],"rate_of_pay":{"id":"fdd96a57-6f7d-43df-be39-73a4f5af0709"},"shift_breaks":[{"break":{"duration":45,"id":"50490227-790e-448a-b191-1cd4a4a6afea"}}],"shift_type":{"id":"68f1a80c-3e72-4b7e-9542-c81b964600c7","name":"Nurse"},"start_datetime":"2019-04-20T07:47:58Z","user_working_shift":{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}},"properties":{"agency":{"properties":{"id":{"description":"The Sona unique identifier of the agency the shift is assigned to. This will be nil unless the shift has been explicitly assigned to an agency.","type":"string","x-nullable":true}},"type":"object"},"category":{"description":"The type of shift being returned","enum":["open_shift","externally_managed","manual_assignment_only","assigned_to_unknown_agency_worker","deleted_draft","externally_managed_annual_leave","gap"],"type":"string"},"description":{"description":"The text description of the shift, or null when none is set","type":"string","x-nullable":true},"draft_of_shift":{"properties":{"id":{"description":"Sona's unique identifier of the original shift that this shift is a draft of, or null when it isn't a draft","type":"string","x-nullable":true}},"type":"object"},"employment_period":{"description":"The employment period the shift belongs to. Null when the shift has none.","properties":{"id":{"description":"Sona unique identifier of the employment period","type":"string"}},"required":["id"],"type":"object","x-nullable":true},"filling_way":{"description":"How the worker was placed onto this shift once it exists as a row in Sona. Integrations can join pay lines (or other payroll artefacts) to shifts using `shift_id` and use this field to distinguish open-shift pool pickups from manager-rostered work.\n\n**booked** — The worker claimed this shift from the open shift pool (self-service). Use this value when a policy must apply only to hours that originated from the pool rather than from direct roster assignment.\n\n**assigned** — The worker was placed on the shift via manager-led rostering or another workflow Sona records as assignment (not self-service pool booking).\n\n**null** — No fill provenance is stored. This may indicate legacy data, an unclaimed shift slot, imports, or other paths that never set the field. **Do not treat `null` as evidence of either booked or assigned behaviour**; agree an explicit default with the customer where this matters (for example payroll or drawdown rules).\n","enum":["booked","assigned"],"type":"string","x-nullable":true},"finish_datetime":{"description":"The finish date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"internal_id":{"description":"The client specific internal ID of a shift, or null when not set","type":"string","x-nullable":true},"is_shift_run":{"description":"Indicates whether this shift is a shift run parent (true) or a regular shift (false)","type":"boolean"},"is_travel_gap":{"description":"Indicates whether this shift is a travel gap between shifts in a shift run. Will be true only when category is 'gap'","type":"boolean"},"org_unit":{"properties":{"id":{"description":"Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"people_we_support":{"description":"People we support (service users/assignments) linked to this shift. Returns an empty list if none are linked.","items":{"properties":{"id":{"description":"The Sona unique identifier of the person we support","type":"string"},"integration_idx":{"description":"The external integration identifier","type":"string"},"name":{"description":"The name of the person we support","type":"string"}},"required":["name","id"],"type":"object"},"type":"array"},"rate_of_pay":{"properties":{"id":{"description":"Sona's unique identifier of the rate of pay associated with this shift, or null when none is calculated","type":"string","x-nullable":true}},"type":"object"},"shift_breaks":{"description":"Breaks defined for this Shift. Returns an empty list if Shift has no breaks","items":{"properties":{"break":{"properties":{"duration":{"description":"The duration of a break represented in minutes","type":"integer"},"id":{"description":"The Sona unique identifier for the shift break","type":"string"}},"required":["id"],"type":"object"}},"type":"object"},"type":"array"},"shift_run_id":{"description":"The Sona unique identifier of the shift run parent this shift belongs to. Will be nil for regular shifts and shift run parents","type":"string","x-nullable":true},"shift_type":{"properties":{"id":{"description":"The Sona unique identifier of the shift type","type":"string"},"name":{"description":"The shift type name","type":"string"}},"required":["id"],"type":"object"},"start_datetime":{"description":"The start date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_working_shift":{"properties":{"id":{"description":"the Sona unique identifier of the user working the shift, will be nil if no user is assigned","type":"string","x-nullable":true}},"required":["id"],"type":"object"}},"required":["shift_type","org_unit","is_travel_gap","is_shift_run","description","category","finish_datetime","start_datetime","id"],"title":"Shift","type":"object"},"ShiftForIndex":{"description":"A single shift as returned by the shifts list endpoint. Fields match the Shift schema except `rate_of_pay` is omitted from index responses for performance. See `filling_way` for how the worker was placed on the shift.","example":{"agency":{"id":"fdd96a57-6f7d-43df-be39-73a4f5af0709"},"category":"open_shift","description":"Description of the shift","draft_of_shift":{"id":"4139288a-1dc8-45f8-b39e-f9c4700b812b"},"employment_period":{"id":"3b1e8f02-6c4d-4f7a-9d51-2a8c0e7b4f19"},"filling_way":"booked","finish_datetime":"2019-04-20T15:47:58Z","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","internal_id":"RGM15RMC","is_shift_run":false,"is_travel_gap":false,"org_unit":{"id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"people_we_support":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","integration_idx":"PWS-001","name":"Jane Smith"}],"shift_breaks":[{"break":{"duration":45,"id":"50490227-790e-448a-b191-1cd4a4a6afea"}}],"shift_type":{"id":"68f1a80c-3e72-4b7e-9542-c81b964600c7","name":"Nurse"},"start_datetime":"2019-04-20T07:47:58Z","user_working_shift":{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}},"properties":{"agency":{"properties":{"id":{"description":"The Sona unique identifier of the agency the shift is assigned to. This will be nil unless the shift has been explicitly assigned to an agency.","type":"string","x-nullable":true}},"type":"object"},"category":{"description":"The type of shift being returned","enum":["open_shift","externally_managed","manual_assignment_only","assigned_to_unknown_agency_worker","deleted_draft","externally_managed_annual_leave","gap"],"type":"string"},"description":{"description":"The text description of the shift, or null when none is set","type":"string","x-nullable":true},"draft_of_shift":{"properties":{"id":{"description":"Sona's unique identifier of the original shift that this shift is a draft of, or null when it isn't a draft","type":"string","x-nullable":true}},"type":"object"},"employment_period":{"description":"The employment period the shift belongs to. Null when the shift has none.","properties":{"id":{"description":"Sona unique identifier of the employment period","type":"string"}},"required":["id"],"type":"object","x-nullable":true},"filling_way":{"description":"How the worker was placed onto this shift once it exists as a row in Sona. Integrations can join pay lines (or other payroll artefacts) to shifts using `shift_id` and use this field to distinguish open-shift pool pickups from manager-rostered work.\n\n**booked** — The worker claimed this shift from the open shift pool (self-service). Use this value when a policy must apply only to hours that originated from the pool rather than from direct roster assignment.\n\n**assigned** — The worker was placed on the shift via manager-led rostering or another workflow Sona records as assignment (not self-service pool booking).\n\n**null** — No fill provenance is stored. This may indicate legacy data, an unclaimed shift slot, imports, or other paths that never set the field. **Do not treat `null` as evidence of either booked or assigned behaviour**; agree an explicit default with the customer where this matters (for example payroll or drawdown rules).\n","enum":["booked","assigned"],"type":"string","x-nullable":true},"finish_datetime":{"description":"The finish date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"internal_id":{"description":"The client specific internal ID of a shift, or null when not set","type":"string","x-nullable":true},"is_shift_run":{"description":"Indicates whether this shift is a shift run parent (true) or a regular shift (false)","type":"boolean"},"is_travel_gap":{"description":"Indicates whether this shift is a travel gap between shifts in a shift run. Will be true only when category is 'gap'","type":"boolean"},"org_unit":{"properties":{"id":{"description":"Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"people_we_support":{"description":"People we support (service users/assignments) linked to this shift. Returns an empty list if none are linked.","items":{"properties":{"id":{"description":"The Sona unique identifier of the person we support","type":"string"},"integration_idx":{"description":"The external integration identifier","type":"string"},"name":{"description":"The name of the person we support","type":"string"}},"required":["name","id"],"type":"object"},"type":"array"},"shift_breaks":{"description":"Breaks defined for this Shift. Returns an empty list if Shift has no breaks","items":{"properties":{"break":{"properties":{"duration":{"description":"The duration of a break represented in minutes","type":"integer"},"id":{"description":"The Sona unique identifier for the shift break","type":"string"}},"required":["id"],"type":"object"}},"type":"object"},"type":"array"},"shift_run_id":{"description":"The Sona unique identifier of the shift run parent this shift belongs to. Will be nil for regular shifts and shift run parents","type":"string","x-nullable":true},"shift_type":{"properties":{"id":{"description":"The Sona unique identifier of the shift type","type":"string"},"name":{"description":"The shift type name","type":"string"}},"required":["id"],"type":"object"},"start_datetime":{"description":"The start date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_working_shift":{"properties":{"id":{"description":"the Sona unique identifier of the user working the shift, will be nil if no user is assigned","type":"string","x-nullable":true}},"required":["id"],"type":"object"}},"required":["shift_type","org_unit","is_travel_gap","is_shift_run","description","category","finish_datetime","start_datetime","id"],"title":"Shift","type":"object"},"ShiftParameters":{"description":"Parameters for creating or updating a shift","example":{"category":"open_shift","description":"Description of the shift","employment_period":{"id":"3b1e8f02-6c4d-4f7a-9d51-2a8c0e7b4f19"},"finish_datetime":"2019-04-20T15:47:58Z","internal_id":"RGM15RMC","org_unit":{"id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"shift_type":{"id":"68f1a80c-3e72-4b7e-9542-c81b964600c7"},"start_datetime":"2019-04-20T07:47:58Z","user_working_shift":{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}},"properties":{"category":{"description":"The type of shift to be created","enum":["open_shift","externally_managed","manual_assignment_only"],"type":"string"},"description":{"description":"The text description of the shift","type":"string"},"employment_period":{"description":"The employment period the shift belongs to. Optional. Used only for regular workers when overlapping employment periods are enabled. Find IDs with GET /api/v1/employment_periods.","properties":{"id":{"description":"Sona unique identifier of the employment period","type":"string"}},"required":["id"],"type":"object"},"finish_datetime":{"description":"The finish date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"internal_id":{"description":"The client specific internal ID of a shift","type":"string"},"org_unit":{"properties":{"id":{"description":"Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"shift_type":{"properties":{"id":{"description":"The Sona unique identifier of the shift type","type":"string"}},"required":["id"],"type":"object"},"start_datetime":{"description":"The start date time of the shift in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"user_working_shift":{"description":"The user assigned to work the shift. Optional — omit for open shifts with no assigned user.","properties":{"id":{"description":"the Sona unique identifier of the user working the shift, will be nil if no user is assigned","type":"string"}},"type":"object"}},"required":["shift_type","org_unit","category","finish_datetime","start_datetime"],"title":"Shift Parameters","type":"object"},"ShiftType":{"description":"A single shift type","example":{"color":"green","default_end_time":"21:50:00","default_shift_break_duration":100,"default_start_time":"07:50:00","description":"Shift description","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","is_absence":true,"is_sleep":true,"is_unpaid_absence_with_shifts":true,"max_shift_length_minutes":720,"min_shift_length_minutes":480,"name":"An example shift type","night_shift":true,"org_unit":{"id":"e27ad021-aa0e-4ccf-91e8-1c7c84656c93"},"peak_shift":true,"shift_tags":["tag1","tag2"],"type_of_work":{"id":"e27ad021-aa0e-4ccf-91e8-1c3c84626c91"}},"properties":{"color":{"description":"Shift color","enum":["amber","blue","emerald","fuchsia","green","indigo","lime","orange","pink","purple","red","rose","teal","violet","yellow"],"type":"string"},"default_end_time":{"description":"Default end time","type":"string","x-nullable":true},"default_shift_break_duration":{"description":"Default shift break duration","type":"integer","x-nullable":true},"default_start_time":{"description":"Default start time","type":"string","x-nullable":true},"description":{"description":"Shift type description","type":"string","x-nullable":true},"id":{"description":"The unique Sona identifier of the shift type","type":"string"},"is_absence":{"description":"Whether it's a absence shift type","type":"boolean"},"is_sleep":{"description":"Makes sure that shifts belonging to a shift_type with is_sleep aren't counted towards the contracted hours","type":"boolean"},"is_unpaid_absence_with_shifts":{"description":"Whether organisation wants to have unpaid holiday shifts on the roster","type":"boolean"},"max_shift_length_minutes":{"description":"Optional maximum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"min_shift_length_minutes":{"description":"Optional minimum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"name":{"description":"Shift type name","type":"string"},"night_shift":{"description":"Whether it's a night shift","type":"boolean"},"org_unit":{"description":"The org unit the shift type belongs to","properties":{"id":{"description":"The unique Sona identifier of the org_unit","type":"string"}},"type":"object"},"peak_shift":{"description":"Whether it's a peak shift","type":"boolean"},"shift_tags":{"description":"Shift type tag","items":{"type":"string"},"type":"array"},"type_of_work":{"description":"The type of work of the shift type","properties":{"id":{"description":"The unique Sona identifier of the type of work","type":"string"}},"type":"object"}},"title":"ShiftType","type":"object"},"ShiftTypeCreateParams":{"description":"Shift type params","example":{"color":"green","default_end_time":"21:50:00","default_shift_break_duration":100,"default_start_time":"07:50:00","description":"Shift description","is_absence":true,"is_sleep":true,"is_unpaid_absence_with_shifts":true,"max_shift_length_minutes":720,"min_shift_length_minutes":480,"name":"An example shift type","night_shift":true,"org_unit_id":"e27ad021-aa0e-4ccf-91e8-1c7c84656c93","peak_shift":true,"shift_tags":["tag1","tag2"],"staffing_model":"shared","type_of_work_id":"e27ad021-aa0e-4ccf-91e8-1c3c84626c91"},"properties":{"color":{"description":"Shift color","enum":["amber","blue","emerald","fuchsia","green","indigo","lime","orange","pink","purple","red","rose","teal","violet","yellow"],"type":"string"},"default_end_time":{"description":"Default end time","type":"string"},"default_shift_break_duration":{"description":"Default shift break duration","type":"integer"},"default_start_time":{"description":"Default start time","type":"string"},"description":{"description":"Shift type description","type":"string"},"is_absence":{"description":"Whether it's a absence shift type","type":"boolean"},"is_sleep":{"description":"Makes sure that shifts belonging to a shift_type with is_sleep aren't counted towards the contracted hours","type":"boolean"},"is_unpaid_absence_with_shifts":{"description":"Whether organisation wants to have unpaid holiday shifts on the roster","type":"boolean"},"max_shift_length_minutes":{"description":"Optional maximum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"min_shift_length_minutes":{"description":"Optional minimum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"name":{"description":"Shift type name","type":"string"},"night_shift":{"description":"Whether it's a night shift","type":"boolean"},"org_unit_id":{"description":"The unique Sona identifier of the org_unit","type":"string"},"peak_shift":{"description":"Whether it's a peak shift","type":"boolean"},"shift_tags":{"description":"Shift type tag","items":{"type":"string"},"type":"array"},"staffing_model":{"description":"Staffing model","enum":["shared","specified"],"type":"string","x-nullable":true},"type_of_work_id":{"description":"The unique Sona identifier of the types_of_work","type":"string"}},"required":["name","type_of_work_id","org_unit_id"],"title":"Shift type params","type":"object"},"ShiftTypePayVariant":{"description":"A single pay variant associated with a shift type.","example":{"counts_towards_annualised_hours":false,"default":true,"derives_annualised_hours_from_shifts":false,"id":"e96de533-85c6-46d4-af7a-1f054783741e","name":"Unpaid time off","paid":false,"subject_to_annualised_hours_bonus":false},"properties":{"counts_towards_annualised_hours":{"description":"Whether counts towards annualised hours or not","type":"boolean"},"default":{"description":"Whether the variant is the default for the shift type or not","type":"boolean"},"derives_annualised_hours_from_shifts":{"description":"Whether derives annualised hours from shifts or not","type":"boolean"},"id":{"description":"Sona pay variant unique identifier","type":"string"},"name":{"description":"The name of the pay variant","type":"string"},"paid":{"description":"Whether the pay variant is paid or not","type":"boolean"},"subject_to_annualised_hours_bonus":{"description":"Whether it's subject to annualised hours bonus or not","type":"boolean"}},"title":"Pay Variant","type":"object"},"ShiftTypePayVariantParams":{"description":"Pay variant params for a shift type.","example":{"counts_towards_annualised_hours":true,"default":false,"derives_annualised_hours_from_shifts":false,"name":"Bank Holiday Premium","paid":true,"subject_to_annualised_hours_bonus":false},"properties":{"counts_towards_annualised_hours":{"description":"Whether counts towards annualised hours or not","type":"boolean"},"default":{"description":"Whether the variant is the default for the shift type or not","type":"boolean"},"derives_annualised_hours_from_shifts":{"description":"Whether derives annualised hours from shifts or not","type":"boolean"},"name":{"description":"The name of the pay variant","type":"string"},"paid":{"description":"Whether the pay variant is paid or not","type":"boolean"},"subject_to_annualised_hours_bonus":{"description":"Whether it's subject to annualised hours bonus or not","type":"boolean"}},"title":"Pay variant params for a shift type","type":"object"},"ShiftTypePayVariants":{"description":"A collection pay variants associated with a shift type.","items":{"$ref":"#/definitions/ShiftTypePayVariant"},"title":"Pay Variants","type":"array"},"ShiftTypeUpdateParams":{"description":"Shift type params","example":{"color":"green","staffing_model":"shared"},"properties":{"color":{"description":"Shift color","enum":["amber","blue","emerald","fuchsia","green","indigo","lime","orange","pink","purple","red","rose","teal","violet","yellow"],"type":"string"},"default_end_time":{"description":"Default end time","type":"string"},"default_shift_break_duration":{"description":"Default shift break duration","type":"integer"},"default_start_time":{"description":"Default start time","type":"string"},"description":{"description":"Shift type description","type":"string"},"is_sleep":{"description":"Makes sure that shifts belonging to a shift_type with is_sleep aren't counted towards the contracted hours","type":"boolean"},"max_shift_length_minutes":{"description":"Optional maximum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"min_shift_length_minutes":{"description":"Optional minimum expected shift length in minutes (null means no rule)","type":"integer","x-nullable":true},"name":{"description":"Shift type name","type":"string"},"night_shift":{"description":"Whether it's a night shift","type":"boolean"},"shift_tags":{"description":"Shift type tag","items":{"type":"string"},"type":"array"},"staffing_model":{"description":"Staffing model","enum":["shared","specified"],"type":"string","x-nullable":true}},"title":"Shift type params","type":"object"},"ShiftTypes":{"description":"A collection of ShiftTypes","items":{"$ref":"#/definitions/ShiftType"},"title":"ShiftTypes","type":"array"},"Shifts":{"description":"A collection of shifts","items":{"$ref":"#/definitions/ShiftForIndex"},"title":"Shifts","type":"array"},"ShowAbsenceResponse":{"description":"Single absence period","example":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","current_employee_number":"1001","days":[{"date":"2019-04-20","status":"not_sick"}],"documents":[{"document_type":"doctor_fit","document_type_label":"Doctor's fit note","document_url":"https://example.com/document.pdf","id":"fd02d7e9-3887-4aba-a0e5-c6910d911a08"}],"ends_at":"2019-04-20T15:47:58Z","finishes_on_local_date":"2019-04-20T16:47:58+01:00","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","idx":"some-external-id","is_open_ended":false,"is_paid":true,"linked_shifts":[{"end_datetime":"2019-04-20T09:47:58Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","is_shift_run":false,"is_travel_gap":false,"shift_break":{"duration":20},"start_datetime":"2019-04-20T08:47:58Z"}],"payroll_type":"holiday","sick_note_provided":false,"starts_at":"2019-04-20T07:47:58Z","starts_on_local_date":"2019-04-20T08:47:58+01:00","user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","user_internal_id":"EMP-0001"},"properties":{"absence_subtype_id":{"description":"Sona unique identifier of the absence_subtype for this absence","type":"string","x-nullable":true},"absence_type_id":{"description":"Sona unique identifier of the absence_type for this absence","type":"string"},"current_employee_number":{"description":"Identifier of the current employee for this absence","type":"string","x-nullable":true},"days":{"description":"List of days with sick/not sick status","items":{"properties":{"date":{"description":"The date","type":"string"},"status":{"description":"Date status","enum":["sick","not_sick"],"type":"string"}},"type":"object"},"type":"array"},"documents":{"description":"List of documents uploaded for this absence","items":{"properties":{"document_type":{"description":"Legacy document type enum (e.g. doctor_fit, self_certificate, other, return_to_work). Stable for all historically uploaded documents. For types with no legacy equivalent, contains the UUID. Prefer document_type_id for new integrations.","type":"string"},"document_type_id":{"description":"UUID of the absence_document_type config row, or null for un-migrated legacy values","type":"string","x-nullable":true},"document_type_label":{"description":"Human-readable name of the document type","type":"string"},"document_url":{"description":"A short-lived URL to download the document","type":"string"},"id":{"description":"Unique identifier of the document","type":"string"}},"type":"object"},"type":"array"},"ends_at":{"description":"UTC Datetime when this absence period ends in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"finishes_on_local_date":{"description":"ends_at converted to UK local time (Europe/London), as an ISO 8601 datetime with a UTC offset rather than Z. An end at local midnight is moved back one second so it falls on the last day of the absence, e.g. 2019-07-20T23:59:59+01:00","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"idx":{"description":"An external ID for this absence period","type":"string","x-nullable":true},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"is_paid":{"description":"Whether this absence is paid. Derived from the absence type's payroll_type. false for unpaid_leave, true for other payroll types, null when payroll_type is not set.","type":"boolean","x-nullable":true},"linked_shifts":{"$ref":"#/definitions/LinkedShifts","description":"A collection of shift ids, start and end datetime that are contained within an absence period"},"payroll_type":{"description":"The payroll classification of this absence's type. One of: sickness_ssp, unpaid_leave, holiday, working, sickness_csp, or null.","type":"string","x-nullable":true},"sick_note_provided":{"description":"Whether a sick note has been provided for this absence. The SSP sick note policy reads it: when an absence is long enough to need a note, false limits SSP to the days that do not need one. null when it was never recorded.","type":"boolean","x-nullable":true},"starts_at":{"description":"UTC Datetime when this absence period starts in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"starts_on_local_date":{"description":"starts_at converted to UK local time (Europe/London), as an ISO 8601 datetime with a UTC offset rather than Z, e.g. 2019-07-20T08:47:58+01:00","type":"string"},"user_id":{"description":"Sona unique identifier of the user for this absence","type":"string"},"user_internal_id":{"description":"Internal identifier of the user for this absence","type":"string","x-nullable":true}},"required":["linked_shifts","current_employee_number","user_internal_id","user_id","absence_type_id","ends_at","starts_at","id"],"title":"Show absence","type":"object"},"ShowAmendmentResponse":{"description":"Result when amending an absence period","example":{"absence":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"df45bc78-c5ee-493c-972d-55d0f5041b1e","ends_at":"2025-01-13T00:00:00Z","id":"4e0a40d6-593e-492a-a57c-7069c9cd5aff","is_open_ended":false,"starts_at":"2025-01-12T00:00:00Z"},"shifts":{"cancelled":[{"absence_period_id":"4e0a40d6-593e-492a-a57c-7069c9cd5aff","cancelled_at":"2025-04-04T00:00:00Z","end_datetime":"2025-01-13T00:00:00Z","id":"cbea106a-ade7-4a9e-8f5c-8fcf0cc5fd9a","org_unit_id":"8de4a59e-7488-4631-b809-c675c39329ba","start_datetime":"2025-01-12T00:00:00Z","user_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb"}],"updated":[{"absence_period_id":"4e0a40d6-593e-492a-a57c-7069c9cd5aff","end_datetime":"2025-01-15T00:00:00Z","id":"3c387e28-99e1-4aae-ac02-083d5b5bd9bb","org_unit_id":"8de4a59e-7488-4631-b809-c675c39329ba","start_datetime":"2025-01-14T00:00:00Z","user_id":"ca977f36-e6ca-4828-8ff2-36860d5359cb"}]}},"properties":{"absence":{"$ref":"#/definitions/AmendedAbsence","description":"The amended absence period"},"shifts":{"description":"The amended absence period","properties":{"cancelled":{"$ref":"#/definitions/AmendedShifts","description":"List of cancelled shifts"},"updated":{"$ref":"#/definitions/AmendedShifts","description":"List of updated shifts"}},"required":["updated","cancelled"],"type":"object"}},"required":["shifts","absence"],"title":"Amendment to Absence Period","type":"object"},"SkillAttribute":{"description":"Skill or attribute","example":{"externally_managed":false,"id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a","idx":"some-external-identifier","name":"dummy","org_unit_ids":["fefc811b-f372-4472-9f28-31fb42add989"],"organisation_id":"278612aa-4fb2-4697-bbc9-196375de6ef5","required_by_default":true,"show_on_roster":true,"show_on_shifts":true,"type":"skill"},"properties":{"externally_managed":{"description":"Whether the skill or attribute is managed externally (e.g. by an LMS). Externally-managed entries cannot be assigned, edited or removed via the Sona UI unless the user has the appropriate admin permissions.","type":"boolean"},"id":{"description":"The identifier of the skill or attribute","type":"string"},"idx":{"description":"An external identifier for the skill or attribute","type":"string","x-nullable":true},"name":{"description":"Skill or attribute name","type":"string"},"org_unit_ids":{"description":"The identifiers of the org units the skill or attribute is applicable to","items":{"type":"string"},"type":"array"},"organisation_id":{"description":"The identifier of the organisation","type":"string"},"required_by_default":{"description":"Whether the skill or attribute is required by default","type":"boolean"},"show_on_roster":{"description":"Whether the skill or attribute should be shown on the roster","type":"boolean"},"show_on_shifts":{"description":"Whether the skill or attribute should be shown on shifts","type":"boolean"},"type":{"description":"Item type","enum":["skill","attribute"],"type":"string"}},"required":["type","name","organisation_id","id"],"title":"Skill or attribute","type":"object"},"SkillAttributeCreateParams":{"description":"Parameters for creating a new skill or attribute","example":{"externally_managed":false,"idx":"SKILL-MH-001","name":"Manual Handling","org_unit_ids":["fefc811b-f372-4472-9f28-31fb42add989"],"organisation_id":"278612aa-4fb2-4697-bbc9-196375de6ef5","required_by_default":true,"show_on_roster":true,"show_on_shifts":false,"type":"skill"},"properties":{"externally_managed":{"description":"Whether the skill or attribute is managed externally (e.g. by an LMS). Defaults to false. Externally-managed entries cannot be assigned, edited or removed via the Sona UI unless the user has the appropriate admin permissions.","type":"boolean"},"idx":{"description":"An external identifier for the skill or attribute","type":"string"},"name":{"description":"Skill or attribute name","type":"string"},"org_unit_ids":{"description":"The identifiers of the org units the skill or attribute is applicable to.\n              Pass an empty list to make the skill or attribute applicable to all org units.","items":{"type":"string"},"type":"array"},"organisation_id":{"description":"The identifier of the organisation","type":"string"},"required_by_default":{"description":"Whether the skill or attribute is required by default","type":"boolean"},"show_on_roster":{"description":"Whether the skill or attribute should be shown on the roster","type":"boolean"},"show_on_shifts":{"description":"Whether the skill or attribute should be shown on shifts","type":"boolean"},"type":{"description":"Item type","enum":["skill","attribute"],"type":"string"}},"required":["type","name","organisation_id"],"title":"Skill or attribute create parameters","type":"object"},"SkillsAndAttributes":{"description":"Skills and attributes","example":[{"externally_managed":false,"id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a","name":"dummy","type":"skill"}],"items":{"properties":{"externally_managed":{"description":"Whether the skill or attribute is managed externally (e.g. by an LMS).","type":"boolean"},"id":{"description":"The identifier of the skill or attribute","type":"string"},"name":{"description":"Skill or attribute name","type":"string"},"type":{"description":"Item type","enum":["skill","attribute"],"type":"string"}},"type":"object"},"title":"Skills and attributes","type":"array"},"StartForecastRunParameters":{"description":"Parameters for starting a forecast run","example":{"started_at":"2026-04-27T09:00:00Z","target":"revenue"},"properties":{"started_at":{"description":"ISO8601 timestamp when the forecast started","type":"string"},"target":{"description":"The forecast target","type":"string"}},"required":["started_at","target"],"title":"StartForecastRunParameters","type":"object"},"StatusRecordData":{"example":{"actioned_by_source":"user_action","budget_impact":"not_charged","end_at":"2026-09-14T00:00:00Z","hold_allocation":true,"hold_expiry":"2026-09-30","id":"3e2d1c0b-a9f8-4e7d-8c6b-5a4f3e2d1c0b","inactive_reason":"Hospital admission","inserted_at_utc":"2026-09-01T08:12:40Z","pws_id":"edfb82b8-0d90-4943-b4df-d27a357c03b5","start_at":"2026-09-01T00:00:00Z","status":"inactive","updated_at_utc":"2026-09-14T09:03:11Z"},"properties":{"actioned_by_source":{"description":"Who recorded it, for example `user_action`","type":"string","x-nullable":true},"budget_impact":{"description":"`charged` or `not_charged`, for inactive records","type":"string","x-nullable":true},"end_at":{"description":"When it ended, UTC ISO 8601. Null while it is the current record.","type":"string","x-nullable":true},"hold_allocation":{"description":"Whether the person's allocation is held","type":"boolean"},"hold_expiry":{"description":"Date the hold expires","type":"string","x-nullable":true},"id":{"description":"Record id","type":"string"},"inactive_reason":{"description":"Why they were inactive","type":"string","x-nullable":true},"inserted_at_utc":{"description":"When the record was created, UTC ISO 8601","type":"string"},"notes":{"description":"Notes","type":"string","x-nullable":true},"notice_date":{"description":"Date notice was given","type":"string","x-nullable":true},"pws_id":{"description":"Sona id of the person we support","type":"string"},"start_at":{"description":"When it started, UTC ISO 8601","type":"string"},"status":{"description":"`active` or `inactive`","type":"string"},"updated_at_utc":{"description":"When the record last changed, for example when it was ended, UTC ISO 8601","type":"string"}},"title":"Status record data","type":"object"},"StatusRecordDataList":{"items":{"$ref":"#/definitions/StatusRecordData"},"title":"Status records","type":"array"},"StoredMetricValue":{"description":"A single recorded metric value","example":{"current":true,"inserted_at":"2026-09-15T05:47:55Z","metric":"Other Revenue","metric_id":"019ef860-7c58-7bb2-9365-b541898355dc","org_unit_id":"923bf60c-4082-4a17-aedd-49a6150d56c4","timestamp":"2026-09-15T00:00:00Z","type":"actual","value":1234.56},"properties":{"current":{"description":"Whether this is the latest recorded version of this value. Sona keeps full history, so a metric/site/timestamp/type can have several versions, of which only one is current.","type":"boolean"},"inserted_at":{"description":"When this version was written to Sona. in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"metric":{"description":"The metric's name, denormalised from `GET /api/v1/metrics`. Always present, but null if the metric definition is no longer available.","type":"string","x-nullable":true},"metric_id":{"description":"Sona unique identifier of the metric","type":"string"},"org_unit_id":{"description":"Sona unique identifier of the org unit (site) the value was recorded against","type":"string"},"timestamp":{"description":"The instant the value applies to. in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string"},"type":{"description":"The kind of value this is","enum":["actual","system_forecasted","human_forecasted","budget","last_year"],"type":"string"},"value":{"description":"The recorded value","type":"number"}},"required":["inserted_at","current","type","value","timestamp","org_unit_id","metric","metric_id"],"title":"StoredMetricValue","type":"object"},"StoredMetricValues":{"description":"A collection of StoredMetricValues","items":{"$ref":"#/definitions/StoredMetricValue"},"title":"StoredMetricValues","type":"array"},"StoredMetricValuesPage":{"description":"One cursor-paginated page of metric values. This endpoint does not return the `page_number` / `total_entries` fields used elsewhere in this API — counting the full result set is not affordable at this data volume.","example":{"entries":[{"current":true,"inserted_at":"2026-09-15T05:47:55Z","metric":"Other Revenue","metric_id":"019ef860-7c58-7bb2-9365-b541898355dc","org_unit_id":"923bf60c-4082-4a17-aedd-49a6150d56c4","timestamp":"2026-09-15T00:00:00Z","type":"actual","value":1234.56}],"next_cursor":"YjNkNGYxYTJ8OTIzYmY2MGMtNDA4Mi00YTE3LWFlZGQtNDlhNjE1MGQ1NmM0fDIwMjYtMDktMTVUMDA6MDA6MDBafDAxOWVmODYxLTJhNDQtN2M5ZS1iMWQyLTZmMGE0YzdlOGIzMXwyMDI2LTA5LTE1VDA1OjQ3OjU1Wg","page_size":1000},"properties":{"entries":{"$ref":"#/definitions/StoredMetricValues","description":"The values in this page"},"next_cursor":{"description":"Pass as `cursor` on the next request, leaving every other parameter unchanged, to fetch the following page. Always present, but null when this is the last page.","type":"string","x-nullable":true},"page_size":{"description":"The number of values requested per page","type":"integer"}},"required":["next_cursor","page_size","entries"],"title":"StoredMetricValuesPage","type":"object"},"Subscription":{"description":"A Webhook Subscription","example":{"event_type":"shift_updated","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","secret_key":"9R5THRIrGPCTKNgv25CAR/wzvgOFhIwz80obPhrxIrl1Wd/8oEEz48xts0UmXUut","url":"https://example.com/api/v1/webhook_events"},"properties":{"event_type":{"description":"The event type this subscription is for","enum":["shift_updated","user_updated","absence_period_created","absence_period_deleted","absence_period_updated","employment_period_created","employment_period_updated","employment_period_started","user_leaving_today","employment_terminated","user_deactivated","user_activated","absence_periods_merged","amendment_created","agency_shift_available","agency_shift_assigned","agency_shift_cancelled_by_agency","agency_shift_cancelled_by_client","agency_shift_updated","user_permissions_changed","job_created","job_updated","job_archived","job_restored","job_deleted","roster_period_locked","roster_period_unlocked","absence_shift_created","absence_shift_updated","absence_shift_deleted","roster_approval_requested","roster_approved","roster_not_approved","roster_site_submitted"],"type":"string"},"id":{"description":"The unique Sona identifier of the subscription","type":"string"},"secret_key":{"description":"The secret key used to create signature, and must be used by client to verify authenticity of the request.\n              The hash generated with HMAC using SHA256 algorithm on combination of \"{timestamp}.{body}\" must match with the signature in the request header.","type":"string"},"url":{"description":"The client API URL, where the event will be pushed to","type":"string"}},"required":["secret_key","url","id"],"title":"Webhook Subscription","type":"object"},"SubscriptionParameters":{"description":"Parameters for subscribing to a webhook on a supplied event type.","example":{"event_type":"shift_updated","url":"https://example.com/api/v1/webhook_events"},"properties":{"event_type":{"description":"The event type this subscription is for","enum":["shift_updated","user_updated","absence_period_created","absence_period_deleted","absence_period_updated","employment_period_created","employment_period_updated","employment_period_started","user_leaving_today","employment_terminated","user_deactivated","user_activated","absence_periods_merged","amendment_created","agency_shift_available","agency_shift_assigned","agency_shift_cancelled_by_agency","agency_shift_cancelled_by_client","agency_shift_updated","user_permissions_changed","job_created","job_updated","job_archived","job_restored","job_deleted","roster_period_locked","roster_period_unlocked","absence_shift_created","absence_shift_updated","absence_shift_deleted","roster_approval_requested","roster_approved","roster_not_approved","roster_site_submitted"],"type":"string"},"url":{"description":"The client API URL, where the event will be pushed to","type":"string"}},"required":["url","event_type"],"title":"Subscription Parameters","type":"object"},"Subscriptions":{"description":"A collection of Subscriptions","example":[{"event_type":"shift_updated","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","secret_key":"9R5THRIrGPCTKNgv25CAR/wzvgOFhIwz80obPhrxIrl1Wd/8oEEz48xts0UmXUut","url":"https://example.com/api/v1/webhook_events"},{"event_type":"absence_period_created","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","secret_key":"9R5THRIrGPCTKNgv25CAR/wzvgOFhIwz80obPhrxIrl1Wd/8oEEz48xts0UmXUut","url":"https://example.com/api/v1/webhook_events"},{"event_type":"absence_period_updated","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","secret_key":"9R5THRIrGPCTKNgv25CAR/wzvgOFhIwz80obPhrxIrl1Wd/8oEEz48xts0UmXUut","url":"https://example.com/api/v1/webhook_events"},{"event_type":"absence_period_deleted","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","secret_key":"9R5THRIrGPCTKNgv25CAR/wzvgOFhIwz80obPhrxIrl1Wd/8oEEz48xts0UmXUut","url":"https://example.com/api/v1/webhook_events"}],"items":{"$ref":"#/definitions/Subscription"},"title":"Subscriptions","type":"array"},"TerminateUserParameters":{"description":"Parameters to terminate a user's employment.","example":{"final_date_of_employment":"2022-01-01","termination_reason_details":"User resigned","termination_reason_id":"fefc811b-f372-4472-9f28-31fb42add989","termination_would_rehire":true},"properties":{"final_date_of_employment":{"description":"The final date of employment","type":"string"},"termination_reason_details":{"description":"Details of the termination","type":"string"},"termination_reason_id":{"description":"The Sona unique identifier of the reason for termination","type":"string"},"termination_would_rehire":{"description":"Would rehire the user","type":"boolean"}},"required":["final_date_of_employment"],"title":"Termination Parameters","type":"object"},"TerminatedUserResponse":{"description":"The response returned after terminating a user's employment. The `active` field is deprecated — use `state` instead for accurate status.","example":{"active":true,"external_id":"befc811b-f379-4472-9f28-31cb41add989","final_date_of_employment":"2026-05-01","first_name":"Louis","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","last_employment_period_id":"c1d8879b-a372-4472-8e28-31fb42ad8989","last_name":"Smith","legal_first_name":"Louis","middle_name":"John","state":"leaver","termination_reason_details":"User resigned","termination_reason_id":"fefc811b-f372-4472-9f28-31fb42add989","termination_would_rehire":true},"properties":{"active":{"description":"[DEPRECATED - use `state` instead] Whether the user is considered active.","type":"boolean"},"external_id":{"description":"The client specific external ID of a user","type":"string","x-nullable":true},"final_date_of_employment":{"description":"User's final date of employment","type":"string","x-nullable":true},"first_name":{"description":"The first name of the user","type":"string"},"id":{"description":"The unique Sona identifier of the user","type":"string"},"last_employment_period_id":{"description":"The Sona unique identifier of the user's latest employment period (the one terminated)","type":"string"},"last_name":{"description":"The last name of the user","type":"string"},"legal_first_name":{"description":"The legal first name of the user","type":"string","x-nullable":true},"middle_name":{"description":"The middle name of the user","type":"string","x-nullable":true},"state":{"description":"The current user state. After termination: `leaver` if `final_date_of_employment` is today or in the future, `left` if it is in the past.","enum":["active","deactivated","leaver","left","starting_soon"],"type":"string"},"termination_reason_details":{"description":"Details of the termination","type":"string","x-nullable":true},"termination_reason_id":{"description":"The Sona unique identifier of the reason for termination, from the latest employment period","type":"string","x-nullable":true},"termination_would_rehire":{"description":"Whether the organisation would rehire the user","type":"boolean","x-nullable":true}},"required":["middle_name","last_name","id","first_name","external_id"],"title":"Terminated Employment","type":"object"},"TerminationReason":{"description":"A single termination reason","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","name":"Resignation"},"properties":{"archived_at":{"description":"When the termination reason was archived, or null if it is still live. Sona's own interface no longer offers archived reasons when recording a new termination, but the API does not reject them: `POST /api/v1/employment_periods` accepts an archived `termination_reason_id`. Filter them out client-side if you do not want them offered. They remain readable because historic employment periods may still reference them. in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","format":"date-time","type":"string","x-nullable":true},"id":{"description":"Sona unique identifier","type":"string"},"name":{"description":"The name of the termination reason","type":"string"}},"required":["name","id"],"title":"TerminationReason","type":"object"},"TerminationReasons":{"description":"A collection of TerminationReasons","items":{"$ref":"#/definitions/TerminationReason"},"title":"TerminationReasons","type":"array"},"TimeAndAttendancePolicy":{"description":"The T&A policy params for the organisational unit.","example":{"attendance_enforced_from":"2025-02-27T12:34:44.932935Z","clock_in_allowed_before_in_minutes":15,"clock_in_and_clock_out_required":true,"clock_out_allowed_after_in_minutes":720,"clocking_requires_location_permission":false,"early_clock_in_tolerance_in_minutes":0,"early_clock_out_tolerance_in_minutes":0,"geotag_required":true,"geotag_required_for_shift_runs":false,"late_clock_in_tolerance_in_minutes":0,"late_clock_out_tolerance_in_minutes":0,"location_tolerance_in_metres":500,"shift_break_attendance_enabled":false,"time_and_attendance_required":false,"unplanned_attendances_enabled":false},"properties":{"attendance_enforced_from":{"description":"UTC date-time from which the T&A policy will be enforced.","format":"datetime","type":"string"},"clock_in_allowed_before_in_minutes":{"description":"The amount of minutes allowed before the clock-in.","type":"integer"},"clock_in_and_clock_out_required":{"description":"Whether or not clocking in and out is required.","type":"boolean"},"clock_out_allowed_after_in_minutes":{"description":"The amount of minutes allowed after the clock-out.","type":"integer"},"clocking_requires_location_permission":{"description":"Whether or not clocking requires the worker to permit location sharing when clocking in/out.","type":"boolean"},"early_clock_in_tolerance_in_minutes":{"description":"What's the tolerance (in minutes) for clocking in early.","type":"integer"},"early_clock_out_tolerance_in_minutes":{"description":"What's the tolerance (in minutes) for clocking out early.","type":"integer"},"geotag_required":{"description":"Whether or not the geotag is required when clocking in/out.","type":"boolean"},"geotag_required_for_shift_runs":{"description":"Whether or not the geotag is required when clocking in/out of a shift run.","type":"boolean"},"late_clock_in_tolerance_in_minutes":{"description":"What's the tolerance (in minutes) for clocking in late.","type":"integer"},"late_clock_out_tolerance_in_minutes":{"description":"What's the tolerance (in minutes) for clocking out late.","type":"integer"},"location_tolerance_in_metres":{"description":"The geotag location tolerance (in meters) when clocking in/out.","type":"integer"},"shift_break_attendance_enabled":{"description":"Whether or not shift-break attendance should be enabled.","type":"boolean"},"time_and_attendance_required":{"description":"Whether or not the T&A is mandatory.","type":"boolean"},"unplanned_attendances_enabled":{"description":"Whether or not unplanned attendances should be enabled.","type":"boolean"}},"required":["shift_break_attendance_enabled","unplanned_attendances_enabled","clock_in_and_clock_out_required","attendance_enforced_from","clock_out_allowed_after_in_minutes","clock_in_allowed_before_in_minutes","location_tolerance_in_metres","clocking_requires_location_permission","geotag_required_for_shift_runs","geotag_required","late_clock_out_tolerance_in_minutes","early_clock_out_tolerance_in_minutes","late_clock_in_tolerance_in_minutes","early_clock_in_tolerance_in_minutes","time_and_attendance_required"],"title":"Organisational Unit's Time & Attendance (T&A) Policy Params","type":"object"},"TimeAndAttendancePolicyParams":{"description":"The T&A policy params for the organisational unit.","example":{"attendance_enforced_from":"2025-02-27T12:34:44.932935Z","clock_in_allowed_before_in_minutes":15,"clock_in_and_clock_out_required":true,"clock_out_allowed_after_in_minutes":720,"clocking_requires_location_permission":false,"early_clock_in_tolerance_in_minutes":0,"early_clock_out_tolerance_in_minutes":0,"geotag_required":true,"geotag_required_for_shift_runs":false,"late_clock_in_tolerance_in_minutes":0,"late_clock_out_tolerance_in_minutes":0,"location_tolerance_in_metres":500,"shift_break_attendance_enabled":false,"time_and_attendance_required":false,"unplanned_attendances_enabled":false},"properties":{"attendance_enforced_from":{"description":"UTC date-time from which the T&A policy will be enforced.","format":"datetime","type":"string"},"clock_in_allowed_before_in_minutes":{"default":15,"description":"The amount of minutes allowed before the clock-in. It will default to the orgnisation's T&A policy if it has one. Otherwise, it will be set to 15 minutes.","type":"integer"},"clock_in_and_clock_out_required":{"default":true,"description":"Whether or not clocking in and out is required. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to true.","type":"boolean"},"clock_out_allowed_after_in_minutes":{"default":15,"description":"The amount of minutes allowed after the clock-out. It will default to the orgnisation's T&A policy if it has one. Otherwise, it will be set to 720 minutes.","type":"integer"},"clocking_requires_location_permission":{"default":false,"description":"Whether or not clocking requires the worker to permit location sharing when clocking in/out. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to false.","type":"boolean"},"early_clock_in_tolerance_in_minutes":{"default":0,"description":"What's the tolerance (in minutes) for clocking in early. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to 0 minutes.","type":"integer"},"early_clock_out_tolerance_in_minutes":{"default":0,"description":"What's the tolerance (in minutes) for clocking out early. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to 0 minutes.","type":"integer"},"geotag_required":{"default":true,"description":"Whether or not the geotag is required when clocking in/out. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to false.","type":"boolean"},"geotag_required_for_shift_runs":{"description":"Whether or not the geotag is required when clocking in/out of a shift run. If omitted, the existing policy's value is kept on update; on create it comes from the organisation's T&A policy, or false if there is none.","type":"boolean"},"late_clock_in_tolerance_in_minutes":{"default":0,"description":"What's the tolerance (in minutes) for clocking in late. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to 0 minutes.","type":"integer"},"late_clock_out_tolerance_in_minutes":{"default":0,"description":"What's the tolerance (in minutes) for clocking out late. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to 0 minutes.","type":"integer"},"location_tolerance_in_metres":{"default":500,"description":"The geotag location tolerance (in meters) when clocking in/out. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to 500 meters.","type":"integer"},"shift_break_attendance_enabled":{"default":false,"description":"Whether or not shift-break attendance should be enabled. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to false.","type":"boolean"},"time_and_attendance_required":{"default":false,"description":"Whether or not the T&A is mandatory. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to false.","type":"boolean"},"unplanned_attendances_enabled":{"default":false,"description":"Whether or not unplanned attendances should be enabled. It will default to the organisation's T&A policy if it has one. Otherwise, it will be set to false.","type":"boolean"}},"required":["attendance_enforced_from"],"title":"Organisational Unit's Time & Attendance (T&A) Policy Params","type":"object"},"TrainingCourse":{"description":"A single Training Course","example":{"archived":false,"description":"In-depth guide to safety in the workplace","frequency":"annually","id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","idx":"safety-training-101","mandatory":true,"name":"Safety Training","provider":"Safety Solutions Inc.","type":"online"},"properties":{"archived":{"description":"Whether the course is archived","type":"boolean"},"archived_at":{"description":"Course archival date, or null when the course is not archived","type":"string","x-nullable":true},"description":{"description":"Course description, or null","type":"string","x-nullable":true},"frequency":{"description":"Course frequency, or null when not set","enum":["once","weekly","bi_weekly","monthly","quarterly","bi_annually","annually","every_2_years","every_3_years","every_4_years","every_5_years"],"type":"string","x-nullable":true},"id":{"description":"The unique identifier of the training course","type":"string"},"idx":{"description":"An external unique identifier for the training course, or null","type":"string","x-nullable":true},"mandatory":{"description":"Whether the course is mandatory for employees in the organisation","type":"boolean"},"name":{"description":"Course name","type":"string"},"provider":{"description":"Course provider, or null","type":"string","x-nullable":true},"type":{"description":"Course type","type":"string","x-nullable":true}},"title":"TrainingCourse","type":"object"},"TrainingCourseAttendance":{"description":"A single Training Course Attendance","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","idx":"external-idx-123","training_course_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","user_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93"},"properties":{"end_date":{"description":"The end date of the training course attendance","type":"string"},"expiry_date":{"description":"The expiry date of the training course attendance","type":"string"},"id":{"description":"The unique identifier of the training course attendance","type":"string"},"idx":{"description":"An external unique identifier for the training course attendance","type":"string","x-nullable":true},"start_date":{"description":"The start date of the training course attendance","type":"string"},"training_course_id":{"description":"The unique identifier of the training course","type":"string"},"training_course_idx":{"description":"The external id of the training course","type":"string"},"user_id":{"description":"The unique identifier of the user","type":"string"},"user_idx":{"description":"The external id of the user","type":"string"}},"title":"TrainingCourseAttendance","type":"object"},"TrainingCourseAttendances":{"description":"A list of Training Course Attendances","items":{"$ref":"#/definitions/TrainingCourseAttendance"},"title":"TrainingCourseAttendances","type":"array"},"TrainingCourses":{"description":"A list of all available training courses for the organisation","items":{"$ref":"#/definitions/TrainingCourse"},"title":"TrainingCourses","type":"array"},"TrainingVenue":{"description":"A training venue with its location, Training shift type and Training type of work","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","is_base_entity":true,"location":{"address_line_1":"1 Training Street","city":"Leeds","country":"United Kingdom","country_code":"GB","id":"0f3e2d4c-5b6a-4c7d-8e9f-0a1b2c3d4e5f","latitude":53.796,"longitude":-1.547,"name":"Leeds Training Centre","postcode":"LS1 4AP"},"name":"Leeds Training Centre","org_unit_type":{"id":"e27ad021-aa0e-4ccf-91e8-1c3c84626c91","name":"Training venue"},"parent_org_unit":{"id":"e27ad021-aa0e-4ccf-91e8-1c7c84656c93"},"timezone":"Europe/London","training_shift_type":{"id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","name":"Training"},"training_type_of_work":{"id":"9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f","name":"Training"}},"properties":{"id":{"description":"Sona unique identifier of the venue org unit","type":"string"},"is_base_entity":{"description":"Always true: a venue is a rosterable site","type":"boolean"},"location":{"$ref":"#/definitions/OrgUnitLocation"},"name":{"description":"The name of the venue","type":"string"},"org_unit_type":{"properties":{"id":{"description":"Sona unique identifier of the \"Training venue\" org unit type","type":"string"},"name":{"description":"The name of the org unit type","type":"string"}},"type":"object"},"parent_org_unit":{"properties":{"id":{"description":"Sona unique identifier of the parent org unit","type":"string"}},"type":"object"},"timezone":{"description":"The timezone of the venue","type":"string"},"training_shift_type":{"properties":{"id":{"description":"Sona unique identifier of the venue's \"Training\" shift type","type":"string"},"name":{"description":"The name of the shift type","type":"string"}},"type":"object"},"training_type_of_work":{"properties":{"id":{"description":"Sona unique identifier of the organisation's \"Training\" type of work","type":"string"},"name":{"description":"The name of the type of work","type":"string"}},"type":"object"}},"title":"TrainingVenue","type":"object"},"TrainingVenueCreateParams":{"description":"Training venue params","example":{"address_line_1":"1 Training Street","city":"Leeds","country_code":"GB","latitude":53.796,"longitude":-1.547,"name":"Leeds Training Centre","parent_id":"e27ad021-aa0e-4ccf-91e8-1c7c84656c93","postcode":"LS1 4AP","timezone":"Europe/London"},"properties":{"address_line_1":{"description":"The first line of address","type":"string"},"address_line_2":{"description":"The second line of address","type":"string"},"address_line_3":{"description":"The third line of address","type":"string"},"address_line_4":{"description":"The fourth line of address","type":"string"},"city":{"description":"The city the venue is in","type":"string"},"country_code":{"default":"GB","description":"The two-letter code representing the country the venue is in","enum":["AC","AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CP","CQ","CR","CU","CV","CW","CX","CY","CZ","DE","DG","DJ","DK","DM","DO","DZ","EA","EC","EE","EG","EH","ER","ES","ET","EU","EZ","FI","FJ","FK","FM","FO","FR","GA","GB","GD","GE","GF","GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","IC","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","QO","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TA","TC","TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG","UM","UN","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","XK","YE","YT","ZA","ZM","ZW"],"type":"string"},"latitude":{"description":"The latitude of the venue in decimal degrees, derived from the postcode unless explicitly set","type":"number"},"longitude":{"description":"The longitude of the venue in decimal degrees, derived from the postcode unless explicitly set","type":"number"},"name":{"description":"The name of the venue","type":"string"},"parent_id":{"description":"Sona unique identifier of the parent org unit, which must be an active org unit of the organisation","type":"string"},"postcode":{"description":"The postcode of the venue","type":"string"},"timezone":{"description":"The timezone of the venue. Defaults to Europe/London","type":"string"},"what3words":{"description":"A what3words reference (e.g. filled.count.soap) converted to the venue's coordinates; cannot be sent together with latitude and longitude. The three words are not stored or returned","type":"string"}},"required":["postcode","city","address_line_1","parent_id","name"],"title":"Training venue params","type":"object"},"TypeOfWork":{"description":"A single type of work","example":{"id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","idx":"some-external-id","name":"An example type of work","properties":{"area":"Front of house","tipped":true}},"properties":{"id":{"description":"Sona unique identifier","type":"string"},"idx":{"description":"An external ID for this type of work","type":"string","x-nullable":true},"name":{"description":"The name of the type of work","type":"string"},"properties":{"description":"Additional key-value properties for this type of work","type":"object"}},"required":["name","id"],"title":"TypeOfWork","type":"object"},"TypeOfWorkRef":{"description":"A reference to a type of work by its Sona identifier","example":{"id":"159600fb-3fa9-4687-bbc9-196375de6ee3"},"properties":{"id":{"description":"The unique Sona identifier of the type of work","type":"string"}},"required":["id"],"title":"TypeOfWorkRef","type":"object"},"TypesOfWork":{"description":"A collection of TypesOfWork","items":{"$ref":"#/definitions/TypeOfWork"},"title":"TypesOfWork","type":"array"},"UnLinkedShifts":{"description":"A collection of shift ids, start and end datetime that are contained within an absence period","items":{"$ref":"#/definitions/AbsenceShifts"},"title":"Linked shifts","type":"array"},"UpdateAbsenceParameters":{"description":"Parameters for updating an absence","example":{"create_replacement_shifts":false,"ends_at":"2019-04-20T15:47:58+00:00","is_open_ended":false,"sick_note_provided":false,"starts_at":"2019-04-19T15:47:58+00:00"},"properties":{"create_replacement_shifts":{"default":false,"description":"If open shifts should be created for the shifts that will be linked to the absence period.","type":"boolean"},"ends_at":{"description":"The end date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`). If the absence is open-ended then this is the expected end date.","type":"string"},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"sick_note_provided":{"description":"Whether a sick note has been provided. Optional; when left out the stored value is kept. Must be true or false; the strings \"true\", \"false\", \"1\" and \"0\" are also accepted, null is not. true is only allowed when the absence type is an SSP absence type. The SSP sick note policy reads it: when an absence is long enough to need a note, false limits SSP to the days that do not need one.","type":"boolean"},"starts_at":{"description":"The start date time of the absence in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`).","type":"string"}},"title":"Absence Parameters","type":"object"},"UpdateAbsenceResponse":{"description":"The absence that was updated","example":{"absence_subtype_id":"dbad14a0-e789-4d85-a5e2-1c8a4f1918ed","absence_type_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","days":[{"date":"2019-04-20","status":"not_sick"}],"ends_at":"2019-04-20T15:47:58Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","idx":"some-external-id","is_open_ended":false,"linked_shifts":[{"end_datetime":"2019-04-20T09:47:58Z","id":"9f8bda89-002a-45e0-8660-46e2fff714d2","is_shift_run":false,"is_travel_gap":false,"shift_break":{"duration":20},"start_datetime":"2019-04-20T08:47:58Z"}],"sick_note_provided":false,"starts_at":"2019-04-20T07:47:58Z","unlinked_shifts":[{"end_datetime":"2019-04-21T09:47:58Z","id":"5ac24aa7-dbca-4b53-bf94-5ebeea37114c","is_shift_run":false,"is_travel_gap":false,"shift_break":{"duration":30},"start_datetime":"2019-04-21T08:47:58Z"}],"user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2"},"properties":{"absence_subtype_id":{"description":"Sona unique identifier of the absence_subtype for this absence","type":"string","x-nullable":true},"absence_type_id":{"description":"Sona unique identifier of the absence_type for this absence","type":"string"},"days":{"description":"List of days with sick/not sick status","items":{"properties":{"date":{"description":"The date","type":"string"},"status":{"description":"Date status","enum":["sick","not_sick"],"type":"string"}},"type":"object"},"type":"array"},"ends_at":{"description":"UTC Datetime when this absence period ends in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"id":{"description":"Sona unique identifier","type":"string"},"idx":{"description":"An external ID for this absence period","type":"string","x-nullable":true},"is_open_ended":{"default":false,"description":"If the end date of the absence period is confirmed this should be false.","type":"boolean"},"linked_shifts":{"$ref":"#/definitions/LinkedShifts","description":"A collection of shift ids, start and end datetime that are contained within an absence period"},"sick_note_provided":{"description":"Whether a sick note has been provided for this absence. The SSP sick note policy reads it: when an absence is long enough to need a note, false limits SSP to the days that do not need one. null when it was never recorded.","type":"boolean","x-nullable":true},"starts_at":{"description":"UTC Datetime when this absence period starts in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","type":"string"},"unlinked_shifts":{"$ref":"#/definitions/UnLinkedShifts","description":"A collection of shift ids, start and end datetime that were unlinked from the absence period"},"user_id":{"description":"Sona unique identifier of the user for this absence","type":"string"}},"required":["unlinked_shifts","linked_shifts","user_id","absence_type_id","ends_at","starts_at","id"],"title":"Update absence","type":"object"},"UpdateAssignmentParameters":{"description":"Parameters for updating the dates of an assigned skill or attribute. Both start_date and end_date are optional; if neither is provided the assignment is returned unchanged.","example":{"end_date":"2025-06-30","start_date":"2025-01-01","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"end_date":{"description":"The new end date for the skill assignment","type":"string"},"start_date":{"description":"The new start date for the skill assignment","type":"string"},"user_id":{"description":"The identifier of the user whose skill or attribute assignment should be updated","type":"string"}},"required":["user_id"],"title":"Update skill or attribute assignment parameters","type":"object"},"UpdateAttendanceParameters":{"description":"Parameters to update a training course attendance","example":{"end_date":"2023-01-31","expiry_date":"2023-12-31","start_date":"2023-01-01"},"properties":{"end_date":{"description":"The end date of the training course attendance","type":"string"},"expiry_date":{"description":"The expiry date of the training course attendance","type":"string"},"start_date":{"description":"The start date of the training course attendance","type":"string"}},"title":"Course Attendance Parameters","type":"object"},"UpdateCourseParameters":{"description":"Parameters for updating a course.","example":{"description":"Get up to date with the latest safety legislation","frequency":"annually","mandatory":"true","name":"Safety Trainiing","provider":"Safety Solutions Inc.","type":"online"},"properties":{"description":{"description":"The course description","type":"string"},"frequency":{"description":"Course frequency","enum":["once","weekly","bi_weekly","monthly","quarterly","bi_annually","annually","every_2_years","every_3_years","every_4_years","every_5_years"],"type":"string"},"mandatory":{"description":"Whether the course is mandatory. Must be 'true' or 'false'.","type":"string"},"name":{"description":"The name of the course","type":"string"},"provider":{"description":"The course provider","type":"string"},"type":{"description":"Course type","enum":["online","classroom","workshop","elearning","on_site","other"],"type":"string"}},"title":"Course Parameters","type":"object"},"UpdateExternalMappingParameters":{"description":"Parameters to update an external mapping","example":{"idx":"external_id_of_variable_type","source_type":"general"},"properties":{"idx":{"description":"An identifier for the external mapping","type":"string"},"source_type":{"description":"Type of source the external mapping relates to","enum":["general","role_mapping_peoplexd","absence_peoplexd","pay_group_peoplexd","store_mapping_harri","oracle_hcm_user","oracle_hcm_org_unit","oracle_hcm_job","oracle_hcm_type_of_work","oracle_hcm_absence_type","oracle_hcm_pay_group","paycom_user","paycom_location","paycom_job","paycom_labor_job","paycom_level","paycom_permission_role","paycom_rate","paycom_punch","paycom_position","oracle_simphony_employee","oracle_simphony_location","oracle_simphony_job_code","oracle_simphony_timecard","assignment_idx","budget_allocation_idx","oracle_hcm_job_code","oracle_hcm_position_name","tao_oracle_hcm_location_department","sap_zalaris_employee","sap_zalaris_job","dayforce_employee","dayforce_location","dayforce_job","dayforce_job_type_of_work","dayforce_suspension_status","dayforce_pay_group","dayforce_pay_policy","dayforce_termination_reason"],"type":"string"}},"title":"External mapping parameters","type":"object"},"UpdateParams":{"description":"Fields to update on a custom form entry. Only provided fields are changed. Warning: updating entries may affect payroll records, approval workflows, shifts, employee documents, form requests, data gathering steps, workflows, and HR reports. See the PUT endpoint description for full details.","example":{"age":3,"name":"Updated value"},"title":"Custom form entry update params","type":"object"},"UpdateUserParameters":{"description":"Parameters for updating a user.","example":{"administrative_org_unit":{"id":"fefc811b-f379-4472-9f28-31cb41add989","idx":"some external ID"},"appears_on_roster_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"assignable_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"auth_strategies":[{"key":"user@example.org","type":"email"}],"bank_account":{"account_name":"Personal","account_number":"12345678","bank_name":"Bank","country_code":"GB","sort_code":"12-34-56"},"base_roles":[{"name":"manager"}],"blocked_for_org_units":[{"id":"4ba2cb25-2d7e-4658-95a6-1b6316a8cdd9"}],"contact_email":"jamie_watson_smith@example.com","contact_mobile_number":"07901234440","contact_phone_number":"07496756541","date_of_birth":"01/02/1978","exclude_from_payroll":false,"external_id":"123700","first_name":"James","home_address":{"address_line_1":"24 Seddon Road","address_line_2":"Manchester","address_line_3":"","address_line_4":"","postcode":"MX1 2BR"},"home_tier_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"identifies_as":"non_binary","insurance_number":"NI8682","last_name":"Smith","legal_first_name":"James","manager_org_units":[{"id":"fefc811b-f379-4472-9f28-31cb41add989","to_remove":true},{"id":"435a7e93-3d41-4de2-90af-d125357aa360"}],"marital_status":"single","middle_name":"Watson","next_of_kin":[{"id":"fefc811b-f379-4472-9f28-31cb41add989","to_remove":true},{"first_name":"Sherlock","id":"fefc811b-f379-4472-9f28-31cb41add989","last_name":"Holmes"},{"first_name":"Mary","last_name":"Morstan","relationship":"spouse"}],"preferred_name":"Jamie","read_only_org_units":[{"id":"fefc811b-f379-4472-9f28-31cb41add989","to_remove":true},{"id":"435a7e93-3d41-4de2-90af-d125357aa360"}],"receives_content_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"sex":"male","title":"dr","types_of_work":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"work_email":"jamie_watson_smith@example.com","work_phone_number":"07496756541"},"properties":{"types_of_work":{"description":"Types of work the user is qualified to do, used for shift matching","items":{"properties":{"id":{"description":"The Sona unique identifier of the type of work","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"sex":{"description":"The sex of the user","enum":["female","male"],"type":"string"},"pay_group_ids":{"description":"List containing a single pay group ID to assign the user to. Omit or pass an empty list to leave the current assignment unchanged. Only one pay group per user is supported.","items":{"type":"string"},"type":"array"},"contact_email":{"description":"The contact email of the user","type":"string","x-nullable":true},"insurance_number":{"description":"The UK National Insurance number of the user","type":"string"},"home_address":{"description":"The home address of the user","properties":{"address_line_1":{"description":"First line the user's address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line the user's address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line the user's address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line the user's address","type":"string","x-nullable":true},"city":{"description":"The city the user's address","type":"string","x-nullable":true},"country":{"description":"The country of the user's address","type":"string","x-nullable":true},"postcode":{"description":"The postcode of the user's address","type":"string","x-nullable":true}},"type":"object","x-nullable":true},"base_roles":{"description":"The underlying roles the user has in the system (independent of org unit specific roles). If omitted and the user currently has no roles, Sona will default `roles` to `manager` when `manager_org_units` are being granted, or `read_only_manager` when only `read_only_org_units` are being granted, so that the user can log in to the manager desktop. Existing roles are never overwritten — pass `base_roles` explicitly to change them.","items":{"properties":{"name":{"description":"The name of the role","enum":["manager","read_only_manager","hr_manager","content_manager","hr_and_content_manager","payroll_manager","organisation_admin","organisation_user_admin","payroll_reports_admin","management_dashboard_viewer","roster_approvals"],"type":"string"}},"type":"object"},"type":"array"},"read_only_org_units":{"description":"Org units the user has read only access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"home_tier_org_units":{"description":"Org units for which the user appears in the primary tier (e.g. see's available shifts first). To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"next_of_kin":{"description":"User's next of kin","items":{"properties":{"first_name":{"description":"First Name of the User's kin","type":"string"},"id":{"description":"The id of the next of kin. If empty new Next of Kin will be added","type":"string"},"last_name":{"description":"Last Name of the User's kin","type":"string"},"location":{"properties":{"address_line_1":{"description":"First line of the next of kin address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line of the next of kin address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line of the next of kin address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line of the next of kin address","type":"string","x-nullable":true},"city":{"description":"The city of the next of kin address","type":"string","x-nullable":true},"country":{"description":"The country of the next of kin address","type":"string","x-nullable":true},"id":{"description":"The id of location","type":"string"},"postcode":{"description":"The postcode of the next of kin address","type":"string","x-nullable":true}},"required":["id"],"type":"object","x-nullable":true},"phone":{"description":"UK Phone of next of kin","type":"string"},"relationship":{"description":"Type of relationship","enum":["spouse","sibling","parent","child","sibling_of_parent","child_of_sibling","cousin","grandparent","step_sibling","step_parent","stepchild","sibling_in_law","parent_in_law","child_in_law","friend","partner","other"],"type":"string","x-nullable":true},"to_remove":{"description":"Pass true with id to remove association","type":"boolean"}},"type":"object"},"type":"array"},"work_phone_number":{"description":"The work phone number for the user","type":"string","x-nullable":true},"work_email":{"description":"The work email of the user","type":"string","x-nullable":true},"exclude_from_payroll":{"description":"Whether to exclude the user from payroll integration","type":"boolean"},"identifies_as":{"description":"The gender the user identifies as","enum":["female","male","non_binary","not_entered","other"],"type":"string","x-nullable":true},"receives_content_for_org_units":{"description":"Org units for which the user receives Newsfeed content. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object"},"type":"array"},"external_id":{"description":"The client specific external ID of a user","type":"string"},"auth_strategies":{"description":"Available methods for logging into the system","items":{"properties":{"id":{"description":"The Sona unique identifier of an existing auth strategy. When supplied, that strategy is updated in place instead of a new one being created.","type":"string"},"key":{"description":"The unique key used to identify the user for this strategy","type":"string"},"type":{"description":"The strategy type","enum":["email","mobile_number","okta","microsoft"],"type":"string"}},"required":["key","type"],"type":"object"},"type":"array"},"date_of_birth":{"description":"The DOB of the user","type":"string","x-nullable":true},"marital_status":{"description":"The marital status of the user","enum":["single","in_a_relationship","engaged","married","civil_partnership","separated","divorced","widowed","other"],"type":"string","x-nullable":true},"administrative_org_unit":{"description":"The org unit the user will be associated with for reporting purposes. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"}},"type":"object","x-nullable":true},"middle_name":{"description":"The middle name of the user","type":"string"},"last_name":{"description":"The last name of the user","type":"string"},"bank_account":{"description":"Bank Account details. Send as null to remove.","properties":{"account_name":{"description":"Name of the Account","type":"string"},"account_number":{"description":"Account number","type":"string"},"bank_name":{"description":"Name of the Bank","type":"string"},"country_code":{"description":"Country Code - only GB is supported","enum":["GB"],"type":"string"},"sort_code":{"description":"Sort code of the bank account","type":"string"}},"required":["country_code","sort_code","account_number","account_name"],"type":"object","x-nullable":true},"manager_org_units":{"description":"Org units the user has full manager (write) access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"final_date_of_employment":{"description":"User's final date of employment","type":"string","x-nullable":true},"legal_first_name":{"description":"The legal first name of the user","type":"string","x-nullable":true},"title":{"description":"Title to refer to the user","enum":["mr","mrs","miss","dr","ms","mx"],"type":"string"},"contact_phone_number":{"description":"The contact phone number for the user","type":"string","x-nullable":true},"first_name":{"description":"The first name of the user","type":"string"},"appears_on_roster_for_org_units":{"description":"Org units for which the user should appear on the roster even if they don't have shifts. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"blocked_for_org_units":{"description":"Org units for which the user is blocked. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit. Provide `id` or `idx`.","type":"string"},"idx":{"description":"External ID of the org unit. Provide `id` or `idx`.","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"type":"object"},"type":"array"},"assignable_org_units":{"description":"Org units for which this user can be assigned shifts by the manager. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"preferred_name":{"description":"The preferred name of the user","type":"string"},"contact_mobile_number":{"description":"The contact mobile number for the user","type":"string"}},"title":"User Parameters","type":"object"},"UpdateWorkRestrictionParameters":{"description":"Parameters for updating an existing work restriction.","example":{"is_night_worker":false,"is_under_ewtd":false,"is_under_student_visa":true},"properties":{"is_night_worker":{"description":"Whether the worker is designated as a night worker (EWTD Reg 6)","type":"boolean"},"is_under_ewtd":{"description":"Whether the worker is under the European Working Time Directive","type":"boolean"},"is_under_student_visa":{"description":"Whether the worker is under a student visa","type":"boolean"}},"title":"Update work restriction parameters","type":"object"},"UpdateWorkingPreferenceParameters":{"description":"Parameters for updating an existing working preference. The `days` parameter is optional and will preserve the existing configuration if not provided.","example":{"days":[{"available":true,"name":"monday"},{"available":false,"name":"tuesday"}]},"properties":{"days":{"$ref":"#/definitions/WorkingPreferenceDays","description":"Days of the week the worker prefers to work"},"is_under_ewtd":{"description":"[DEPRECATED - use /api/v1/work_restrictions instead] Whether the worker is under the European Working Time Directive. Written to the worker's work restriction.","type":"boolean"},"is_under_student_visa":{"description":"[DEPRECATED - use /api/v1/work_restrictions instead] Whether the worker is under a student visa. Written to the worker's work restriction.","type":"boolean"}},"title":"Update working preference parameters","type":"object"},"User":{"description":"A User of the Sona System","example":{"types_of_work":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"sex":"male","pay_group_ids":["2de697e7-29b6-498b-b324-00aa5de7c526"],"contact_email":"james.example.smith@sona.com","insurance_number":"QQ123452D","home_address":{"address_line_1":"Baker Street 221","address_line_2":"Marylebone","city":"London","country":"United Kingdom","postcode":"NW1 6XE"},"base_roles":[{"name":"manager"}],"read_only_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"home_tier_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"next_of_kin":[{"first_name":"John","id":"fefc811b-f379-4472-9f28-31cb41add989","last_name":"Watson","phone":"07496756541","relationship":"spouse"}],"work_phone_number":"07496756541","work_email":"james.example.smith@sona.com","skills_and_attributes":[{"name":"Attribute 1","skill_and_attribute_id":"ea72e3cc-b8c6-433a-87e3-d14520e21430"},{"end_date":"2025-06-01","name":"Skill 1","skill_and_attribute_id":"9925c220-a290-464a-9f7e-16f60cd70052","start_date":"2024-06-01"}],"id":"159600fb-3fa9-4687-bbc9-196375de6ee3","exclude_from_payroll":false,"identifies_as":"male","receives_content_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"external_id":"befc811b-f379-4472-9f28-31cb41add989","auth_strategies":[{"id":"fefc811b-f372-4472-9f28-31fb42add989","key":"user@example.org","type":"email"}],"date_of_birth":"2022-01-01","marital_status":"engaged","administrative_org_unit":{"id":"fefc811b-f379-4472-9f28-31cb41add989"},"middle_name":"Watson","last_name":"Smith","state":"active","active":true,"bank_account":{"account_name":"Personal","account_number":"12345678","bank_name":"Bank","country_code":"GB","sort_code":"12-34-56"},"manager_org_units":[{"id":"fefc811b-f379-4472-9f28-31cb41add989"},{"id":"fefc811b-f379-4472-9f28-31fb42add989"}],"legal_first_name":"James","title":"dr","organisation_user_id":"278612aa-4fb2-4697-bbc9-196375de6ef5","contact_phone_number":"07496756541","first_name":"James","appears_on_roster_for_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"blocked_for_org_units":[{"id":"4ba2cb25-2d7e-4658-95a6-1b6316a8cdd9"}],"assignable_org_units":[{"id":"fefc811b-f372-4472-9f28-31fb42add989"}],"preferred_name":"Jim","contact_mobile_number":"07901234440"},"properties":{"types_of_work":{"description":"Types of work the user is qualified to do, used for shift matching","items":{"properties":{"id":{"description":"The Sona unique identifier of the type of work","type":"string"},"to_remove":{"description":"Pass true to remove association","type":"boolean"}},"required":["id"],"type":"object"},"type":"array"},"sex":{"description":"The sex of the user","type":"string","x-nullable":true},"pay_group_ids":{"description":"List of Sona unique identifiers of pay groups","items":{"type":"string"},"type":"array"},"contact_email":{"description":"The contact email for the user","type":"string","x-nullable":true},"insurance_number":{"description":"The user's National Insurance number","type":"string","x-nullable":true},"home_address":{"description":"The home address of the user","properties":{"address_line_1":{"description":"First line the user's address","type":"string","x-nullable":true},"address_line_2":{"description":"Second line the user's address","type":"string","x-nullable":true},"address_line_3":{"description":"Third line the user's address","type":"string","x-nullable":true},"address_line_4":{"description":"Fourth line the user's address","type":"string","x-nullable":true},"city":{"description":"The city the user's address","type":"string","x-nullable":true},"country":{"description":"The country of the user's address","type":"string","x-nullable":true},"postcode":{"description":"The postcode of the user's address","type":"string","x-nullable":true}},"type":"object","x-nullable":true},"base_roles":{"description":"The underlying roles the user has in the system (independent of org unit specific roles","items":{"properties":{"name":{"description":"The name of the role","enum":["manager","read_only_manager","hr_manager","content_manager","hr_and_content_manager","payroll_manager","organisation_admin","organisation_user_admin","payroll_reports_admin","management_dashboard_viewer","roster_approvals"],"type":"string"}},"type":"object"},"type":"array"},"read_only_org_units":{"description":"Org units the user has read only access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"home_tier_org_units":{"description":"Org units for which the user appears in the primary tier (e.g. see's available shifts first)","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"next_of_kin":{"description":"Next of kinds of the user","items":{"properties":{"first_name":{"description":"The first name of the next of kin","type":"string","x-nullable":true},"id":{"description":"The id of the next of kin","type":"string"},"last_name":{"description":"The last name of the next of kin","type":"string","x-nullable":true},"location":{"$ref":"#/definitions/NextOfKinLocation"},"phone":{"description":"The phone number of the next of kin","type":"string","x-nullable":true},"relationship":{"description":"The relationship type of the next of kin","enum":["spouse","sibling","parent","child","sibling_of_parent","child_of_sibling","cousin","grandparent","step_sibling","step_parent","stepchild","sibling_in_law","parent_in_law","child_in_law","friend","partner","other"],"type":"string","x-nullable":true}},"required":["relationship","phone","last_name","first_name","id"],"type":"object"},"type":"array"},"work_phone_number":{"description":"The work phone number for the user","type":"string","x-nullable":true},"work_email":{"description":"The work email of the user","type":"string","x-nullable":true},"skills_and_attributes":{"description":"Skills and attributes for the user. The user_updated webhook currently includes only skill_and_attribute_id, start_date, and end_date for each entry; value is excluded.","items":{"properties":{"end_date":{"description":"The optional date on which the skill ends. Can't be used for attributes.","type":"string","x-nullable":true},"name":{"description":"The name of the skill or attribute","type":"string"},"skill_and_attribute_id":{"description":"The Sona unique identifier of skill or attribute","type":"string"},"start_date":{"description":"The optional date on which the skill starts. Can't be used for attributes.","type":"string","x-nullable":true}},"required":["skill_and_attribute_id"],"type":"object"},"type":"array"},"id":{"description":"The unique Sona identifier of the user","type":"string"},"updated_at_utc":{"description":"When the user or their organisation membership was last updated, whichever is later, as an ISO 8601 UTC datetime","format":"date-time","type":"string","x-nullable":true},"exclude_from_payroll":{"description":"Boolean flag to exclude a user from payroll integration","type":"boolean"},"identifies_as":{"description":"The gender the user identifies as","type":"string","x-nullable":true},"receives_content_for_org_units":{"description":"Org units for which the user receives Newsfeed content. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"external_id":{"description":"The client specific external ID of a user","type":"string","x-nullable":true},"auth_strategies":{"description":"Available methods for logging into the system","items":{"properties":{"id":{"description":"The Sona unique identifier for the auth strategy","type":"string"},"key":{"description":"The unique key used to identify the user for this strategy","type":"string"},"type":{"description":"The strategy type","enum":["email","mobile_number","okta","microsoft"],"type":"string"}},"required":["key","type","id"],"type":"object"},"type":"array"},"date_of_birth":{"description":"The DOB of the user","type":"string","x-nullable":true},"marital_status":{"description":"Marital status of the user","type":"string","x-nullable":true},"agency":{"description":"If the user is an agency worker, the agency they are associated with","properties":{"id":{"description":"The Sona unique identifier of the agency","type":"string","x-nullable":true}},"required":["id"],"type":"object"},"administrative_org_unit":{"description":"The org unit the user will be associated with for reporting purposes","properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object","x-nullable":true},"middle_name":{"description":"The middle name of the user","type":"string","x-nullable":true},"last_name":{"description":"The last name of the user","type":"string"},"state":{"description":"The current user state in the organisation","enum":["active","deactivated","leaver","left","starting_soon","onboarding"],"type":"string","x-nullable":true},"active":{"description":"[DEPRECATED - use `state` instead] Whether the user is considered active.","type":"boolean","x-nullable":true},"bank_account":{"description":"Bank Account details. Send as null to remove.","properties":{"account_name":{"description":"Name of the Account","type":"string"},"account_number":{"description":"Account number","type":"string"},"bank_name":{"description":"Name of the Bank","type":"string"},"country_code":{"description":"Country Code - only GB is supported","enum":["GB"],"type":"string"},"sort_code":{"description":"Sort code of the bank account","type":"string"}},"required":["country_code","sort_code","account_number","account_name"],"type":"object","x-nullable":true},"manager_org_units":{"description":"Org units the user has full manager (write) access to","items":{"properties":{"id":{"description":"The id of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"final_date_of_employment":{"description":"User's final date of employment","type":"string","x-nullable":true},"legal_first_name":{"description":"The legal first name of the user","type":"string","x-nullable":true},"title":{"description":"Title to refer to the user","type":"string","x-nullable":true},"organisation_user_id":{"description":"The Sona unique identifier of the user's membership of the organisation","type":"string","x-nullable":true},"contact_phone_number":{"description":"The contact phone number for the user","type":"string","x-nullable":true},"first_name":{"description":"The first name of the user","type":"string"},"appears_on_roster_for_org_units":{"description":"Org units for which the user should appear on the roster even if they don't have shifts","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"blocked_for_org_units":{"description":"Org units for which the user is blocked. To pass a Sona identifier,\n                  pass it as %{id => sona_org_id} and to pass an external id pass it as %{idx => external_id}.","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"},"idx":{"description":"External ID of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"assignable_org_units":{"description":"Org units for which this user can be assigned shifts by the manager","items":{"properties":{"id":{"description":"The Sona unique identifier of the org unit","type":"string"}},"required":["id"],"type":"object"},"type":"array"},"preferred_name":{"description":"The preferred name of the user","type":"string","x-nullable":true},"contact_mobile_number":{"description":"The contact mobile number for the user","type":"string","x-nullable":true}},"required":["sex","preferred_name","middle_name","marital_status","last_name","identifies_as","id","first_name","external_id"],"title":"User","type":"object"},"UserAuthStrategies":{"description":"A collection of authentication strategies","items":{"$ref":"#/definitions/UserAuthStrategy"},"title":"Auth Strategies","type":"array"},"UserAuthStrategy":{"description":"A single authentication strategy","example":{"id":"e96de533-85c6-46d4-af7a-1f054783741e","key":"user@example.com","type":"email"},"properties":{"id":{"description":"Sona unique identifier of the auth strategy","type":"string"},"key":{"description":"The authentication key value (e.g. email address, phone number)","type":"string"},"type":{"description":"The authentication strategy type","enum":["email","mobile_number","okta","microsoft","adfs","kiosk"],"type":"string"}},"required":["id"],"title":"User Auth Strategy","type":"object"},"UserAuthStrategyUpdateParams":{"description":"Parameters for updating an authentication strategy","example":{"key":"newemail@example.com"},"properties":{"key":{"description":"The new authentication key value","type":"string"}},"required":["key"],"title":"Auth Strategy update params","type":"object"},"UserLookupResponse":{"description":"User plus employment periods. Period objects match `GET /api/v1/employment_periods?user_id=` list entries.","example":{"employment_periods":[{"active":true,"continuous_employment_start_date":"2020-06-15","employee_number":"EMP-00472","finish_date":"2026-12-31","id":"159600fb-3fa9-4687-bbc9-196375de6ee4","job_grade_id":"15bb52f6-7f37-4ac8-84ee-71fbca32106d","job_id":"3214f01b-7264-4c79-846a-8583a75e1d70","level_id":"befc811b-f379-4472-9f28-31cb41add989","organisation_user_id":"7fe0866b-9631-42cb-94b1-d8e426ac0fb0","primary_org_unit_id":"e27ad021-af0e-4ccf-95e8-1c7c84656c93","raffy_tier":"raffy_gm","reports_to_org_user_id":"84f0d476-f2d6-43c4-adbf-b7f952b0af5f","reports_to_user_id":"18c32388-0a1e-442a-9a2f-3e0621cc068a","start_date":"2024-03-01","starter_declaration":"A","student_loan_plan_five":false,"student_loan_plan_four":false,"student_loan_plan_one":false,"student_loan_plan_two":false,"student_loan_post_graduate":false,"user_id":"159600fb-3fa9-4687-bbc9-196375de6ee3"}],"employment_periods_truncated":false,"lookup":{"by":"insurance_number"},"user":{"external_id":"EMP-00472","first_name":"James","id":"159600fb-3fa9-4687-bbc9-196375de6ee3","identifies_as":"male","insurance_number":"QQ123452D","last_name":"Smith","marital_status":"single","middle_name":"Watson","organisation_user_id":"7fe0866b-9631-42cb-94b1-d8e426ac0fb0","preferred_name":"Jim","sex":"male","state":"active"}},"properties":{"employment_periods":{"$ref":"#/definitions/EmploymentPeriods","description":"Periods for this user (start date ascending)"},"employment_periods_truncated":{"description":"Additional periods exist beyond this response","type":"boolean"},"lookup":{"description":"Echo of which query parameter was used to resolve the user","properties":{"by":{"description":"One of: insurance_number, employee_number, external_id, contact_email, email","type":"string"}},"required":["by"],"type":"object"},"user":{"$ref":"#/definitions/User","description":"Resolved user"}},"required":["employment_periods_truncated","employment_periods","user"],"title":"User lookup response","type":"object"},"Users":{"description":"A collection of Users","items":{"$ref":"#/definitions/User"},"title":"Users","type":"array"},"ValidationErrors":{"description":"Changeset validation errors returned when a create or update request fails field-level validation. Keys are field names, values are arrays of error message strings.","example":{"errors":{"email":["can't be blank"],"name":["is too short"]}},"properties":{"errors":{"description":"Object where keys are field names and values are arrays of error messages","type":"object"}},"required":["errors"],"title":"Validation Errors","type":"object"},"WebhookAbsenceEvent":{"description":"Webhook payload for absence_period_created, absence_period_updated, absence_period_deleted, absence_periods_merged.","properties":{"event_type":{"description":"Absence event type","type":"string"},"payload":{"description":"Contains absence object with id, starts_at, ends_at, user_id, absence_type_id, documents, linked_shifts. For deleted: includes unlinked_shifts. For merged: updated_absence_period and deleted_absence_periods.","type":"object"}},"required":["payload","event_type"],"title":"WebhookAbsenceEvent","type":"object"},"WebhookAgencyRef":{"description":"Agency reference in agency shift events.","properties":{"id":{"description":"Agency UUID","type":"string"},"worker_id":{"description":"Assigned worker UUID (nullable)","type":"string"}},"required":["id"],"title":"WebhookAgencyRef","type":"object"},"WebhookAgencyShift":{"description":"Agency shift object included in agency webhook payloads.","properties":{"end_datetime":{"description":"ISO 8601 UTC end time","type":"string"},"end_timezone":{"description":"IANA timezone for end","type":"string"},"id":{"description":"Shift UUID","type":"string"},"location":{"description":"Org unit name (nullable)","type":"string"},"org_unit_id":{"description":"Organisation unit UUID","type":"string"},"pws":{"description":"People we support: [{ assignment_id, name }]","type":"array"},"role":{"description":"Shift type name","type":"string"},"shift_breaks":{"description":"Break periods","type":"array"},"shift_type":{"description":"Structured shift type: { id, name }","type":"object"},"skills":{"description":"Required skills: [{ id, name }]","type":"array"},"start_datetime":{"description":"ISO 8601 UTC start time","type":"string"},"start_timezone":{"description":"IANA timezone for start","type":"string"}},"required":["end_datetime","start_datetime","org_unit_id","id"],"title":"WebhookAgencyShift","type":"object"},"WebhookAgencyShiftData":{"description":"Data envelope for agency shift events.","properties":{"agency":{"$ref":"#/definitions/WebhookAgencyRef","description":"Agency and worker reference"},"metadata":{"description":"Event-specific metadata (e.g. offered_at, assigned_at, cancelled_at)","type":"object"},"organisation_id":{"description":"Organisation UUID","type":"string"},"shift":{"$ref":"#/definitions/WebhookAgencyShift","description":"Shift details"}},"required":["agency","shift","organisation_id"],"title":"WebhookAgencyShiftData","type":"object"},"WebhookAgencyShiftEvent":{"description":"Webhook payload for agency_shift_available, agency_shift_assigned, agency_shift_cancelled_by_agency, and agency_shift_cancelled_by_client events. Top-level has event_type + payload; payload contains event_id, timestamp, and data.","properties":{"event_type":{"description":"One of: agency_shift_available, agency_shift_assigned, agency_shift_cancelled_by_agency, agency_shift_cancelled_by_client","type":"string"},"payload":{"description":"Contains event_id (assignment UUID), timestamp (ISO 8601 UTC), and data (WebhookAgencyShiftData)","type":"object"}},"required":["payload","event_type"],"title":"WebhookAgencyShiftEvent","type":"object"},"WebhookAgencyShiftUpdatedData":{"description":"Data envelope for agency_shift_updated including changes map.","properties":{"agency_id":{"description":"Agency UUID","type":"string"},"changes":{"description":"Map of changed fields, each with old and new values","type":"object"},"metadata":{"description":"Contains updated_at timestamp","type":"object"},"organisation_id":{"description":"Organisation UUID","type":"string"},"shift":{"description":"Full shift snapshot with all fields (id, org_unit_id, user_id, agency_id, start_datetime, end_datetime, role, shift_type, skills, pws, shift_type_id, start_timezone, end_timezone, location, shift_breaks, cancelled_at, deleted_at, description, is_shift_run, shift_run_id, category, absence_period_id)","type":"object"}},"required":["changes","shift","agency_id","organisation_id"],"title":"WebhookAgencyShiftUpdatedData","type":"object"},"WebhookAgencyShiftUpdatedEvent":{"description":"Webhook payload for agency_shift_updated. Top-level has event_type + payload; payload contains event_id, timestamp, and data with full shift snapshot plus changes map.","properties":{"event_type":{"description":"Always agency_shift_updated","type":"string"},"payload":{"description":"Contains event_id (shift_update_{shift_uuid}_{epoch_ms}), timestamp (ISO 8601 UTC), and data (WebhookAgencyShiftUpdatedData)","type":"object"}},"required":["payload","event_type"],"title":"WebhookAgencyShiftUpdatedEvent","type":"object"},"WebhookEmploymentEvent":{"description":"Webhook payload for employment_period_created, employment_period_started, employment_period_updated, employment_terminated.","properties":{"event_type":{"description":"Employment event type","type":"string"},"payload":{"description":"Contains employment_period (with org_unit, reports_to, types_of_work, permission_roles, termination_reason, employment_specs), job (object or message string), and user (except for employment_period_updated)","type":"object"}},"required":["payload","event_type"],"title":"WebhookEmploymentEvent","type":"object"},"WebhookJobEvent":{"description":"Webhook payload for job_created, job_updated, job_archived, job_restored, job_deleted.","example":{"event_type":"job_created","payload":{"job":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Registered Nurse","updated_at":"2026-01-20T09:00:00Z"}}},"properties":{"event_type":{"description":"Job event type","type":"string"},"payload":{"description":"Contains job object with id, name, upload_id, archived_at, updated_at","type":"object"}},"required":["payload","event_type"],"title":"WebhookJobEvent","type":"object"},"WebhookShiftUpdatedEvent":{"description":"Webhook payload for shift_updated (non-absence shifts only). Reports field-level changes.","example":{"event_type":"shift_updated","payload":{"field":"user_working_shift","new_value":"f9e8d7c6-b5a4-3210-fedc-ba0987654321","shift_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},"properties":{"event_type":{"description":"Always shift_updated","type":"string"},"payload":{"description":"Contains shift_id, field (always user_working_shift), old_value (user UUID or null), new_value (user UUID or null)","type":"object"}},"required":["payload","event_type"],"title":"WebhookShiftUpdatedEvent","type":"object"},"WebhookUserEvent":{"description":"Webhook payload for user_updated, user_leaving_today, user_deactivated, user_activated.","properties":{"event_type":{"description":"One of: user_updated, user_leaving_today, user_deactivated, user_activated","type":"string"},"payload":{"description":"Contains user object matching GET /api/v1/users/{id}","type":"object"}},"required":["payload","event_type"],"title":"WebhookUserEvent","type":"object"},"WebhookUserPermissionsEvent":{"description":"Webhook payload for user_permissions_changed.","properties":{"event_type":{"description":"Always user_permissions_changed","type":"string"},"payload":{"description":"Contains user object plus permission_roles array: [{ id, name, description, location_specific }]","type":"object"}},"required":["payload","event_type"],"title":"WebhookUserPermissionsEvent","type":"object"},"WorkRestriction":{"description":"Work restriction","example":{"id":"ea72e3cc-b8c6-433a-87e3-d14520e21430","is_night_worker":false,"is_under_ewtd":false,"is_under_student_visa":false,"worker_id":"9925c220-a290-464a-9f7e-16f60cd70052"},"properties":{"id":{"description":"The identifier of the work restriction","type":"string"},"is_night_worker":{"description":"Whether the worker is designated as a night worker (EWTD Reg 6)","type":"boolean"},"is_under_ewtd":{"description":"Whether the worker is under the European Working Time Directive","type":"boolean"},"is_under_student_visa":{"description":"Whether the worker is under a student visa","type":"boolean"},"worker_id":{"description":"The ID of the worker the restriction is for","type":"string"}},"title":"Work restriction","type":"object"},"WorkRestrictions":{"description":"Returns a list of work restrictions","example":[{"id":"ea72e3cc-b8c6-433a-87e3-d14520e21430","is_night_worker":false,"is_under_ewtd":false,"is_under_student_visa":false,"worker_id":"9925c220-a290-464a-9f7e-16f60cd70052"}],"items":{"$ref":"#/definitions/WorkRestriction"},"title":"Work restrictions","type":"array"},"WorkingPreference":{"description":"Working preference","example":{"days":[{"available":true,"name":"monday"},{"available":false,"name":"tuesday"},{"available":true,"name":"wednesday"},{"available":true,"name":"thursday"},{"available":true,"name":"friday"},{"available":false,"name":"saturday"},{"available":false,"name":"sunday"}],"id":"ea72e3cc-b8c6-433a-87e3-d14520e21430","worker_id":"9925c220-a290-464a-9f7e-16f60cd70052"},"properties":{"days":{"$ref":"#/definitions/WorkingPreferenceDays"},"id":{"description":"The identifier of the working preference","type":"string"},"worker_id":{"description":"The ID of the worker the preference is for","type":"string"}},"title":"Working preference","type":"object"},"WorkingPreferenceDays":{"description":"Days of the week the worker prefers to work","items":{"description":"Day of the week","properties":{"available":{"description":"Whether the worker is available to work on this day","type":"boolean"},"name":{"description":"The name of the day of the week","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"}},"type":"object"},"type":"array"},"WorkingPreferences":{"description":"Returns a list of working preferences","example":[{"days":[{"available":true,"name":"monday"},{"available":false,"name":"tuesday"},{"available":true,"name":"wednesday"},{"available":true,"name":"thursday"},{"available":true,"name":"friday"},{"available":false,"name":"saturday"},{"available":false,"name":"sunday"}],"id":"ea72e3cc-b8c6-433a-87e3-d14520e21430","worker_id":"9925c220-a290-464a-9f7e-16f60cd70052"}],"items":{"$ref":"#/definitions/WorkingPreference"},"title":"Working preferences","type":"array"},"YapsterUserMigrationInfoRequestBody":{"description":"Request body for migrating Yapster user information","example":{"yapster_confirmed_phones":["+447700900123","+447700900456"]},"properties":{"yapster_confirmed_phones":{"items":{"type":"string"},"title":"Yapster User confirmed phone numbers","type":"array"}},"title":"Yapster User Migration Request Body","type":"object"},"YapsterUserMigrationInfoResponse":{"description":"Information required by the Yapster client to migrate a user from the Yapster app to the Sona app.","example":{"migration_info":{"auth_strategy_phones":["+447700900123"],"external_employee_id":"EMP-10042","iat":"2024-06-15T08:00:00Z","matched_phones":["+447700900123"],"migration_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example","organisation_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"}},"properties":{"migration_info":{"properties":{"auth_strategy_phones":{"description":"List of authentication phone numbers","items":{"type":"string"},"type":"array"},"external_employee_id":{"description":"Customer employee ID","type":"string"},"iat":{"description":"Issued at timestamp","type":"string"},"matched_phones":{"description":"List of Yapster confirmed phone numbers matched on Sona user","items":{"type":"string"},"type":"array"},"migration_token":{"description":"Migration token","type":"string"},"organisation_id":{"description":"Sona Organisation ID","type":"string"},"user_id":{"description":"Sona User ID","type":"string"}},"title":"Migration Info Object","type":"object"}},"title":"Yapster User Migration Info","type":"object"},"YearlyHolidayAllowance":{"description":"Yearly holiday allowance","example":{"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","denomination":"hours","id":"550e8400-e29b-41d4-a716-446655440000","start_date":"2019-04-20","user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","yearly_holiday_allowance":28},"properties":{"contract_id":{"description":"The unique Sona identifier of the contract of the absence year, or null for an absence year without a contract","type":"string","x-nullable":true},"denomination":{"description":"Absence year denomination","enum":["hours","days"],"type":"string"},"id":{"description":"The unique identifier of the absence year","type":"string"},"start_date":{"description":"The start date of the holiday year","type":"string"},"user_id":{"description":"The unique Sona identifier of the user entity","type":"string"},"yearly_holiday_allowance":{"description":"The total yearly holiday allowance for the worker","type":"integer"}},"required":["user_id"],"title":"Yearly holiday allowance","type":"object"},"YearlyHolidayAllowanceList":{"description":"Yearly holiday allowance list","items":{"$ref":"#/definitions/YearlyHolidayAllowance"},"title":"Yearly holiday allowance list","type":"array"},"YearlyHolidayAllowanceParameters":{"description":"Yearly holiday allowance parameters","example":{"contract_id":"4f5c2a36-8f6e-4a0a-9d8e-2b1c7f3e6a51","start_date":"2019-04-20","user_id":"9f8bda89-002a-45e0-8660-46e2fff714d2","yearly_allowance":28},"properties":{"contract_id":{"description":"The Sona unique identifier of the contract the request is about, from `contract_id` on `GET /api/v1/employment_periods`. Only used by organisations with overlapping employment periods enabled. Required when the user holds employment periods on several contracts covering the requested date; without it the only contract covering that date is used. A contract whose employment periods have ended still reads its own absence year covering the date.","type":"string"},"start_date":{"description":"The start date holiday year for which to set the entitlement in the following ISO 8601 UTC format: 2019-04-20 (`YYYY-MM-DD`)","type":"string"},"user_id":{"description":"Sona unique identifier of the user for the holiday allowance","type":"string"},"yearly_allowance":{"description":"The total yearly holiday allowance for the worker for a year","type":"integer"}},"required":["yearly_allowance","start_date","user_id"],"title":"Yearly holiday allowance parameters","type":"object"}},"info":{"description":"The Sona Client API provides programmatic access to manage workforce operations including users, shifts, absences, employment periods, and organisational structures.\n\n## Key Concepts\n\n### Authentication\n\nAll requests require a Bearer token in the `Authorization` header. Tokens are scoped to an organisation and issued by the Sona team during integration setup — there is no self-service token endpoint. Contact your Sona representative if you need a new token or if your token has been revoked.\n\n### API Token Permissions & 403 Errors\n\nAPI tokens can be configured with an allowed list of endpoints. If your token is restricted and you attempt to access an endpoint not in the allowed list, the API will return a `403 Forbidden` response with the message: *\"This API credential does not have access to this endpoint\"*. Contact Sona to have the required endpoint(s) added to your token's permissions.\n\nA token with no explicit endpoint restrictions has access to all endpoints.\n\n### Identifiers: Sona IDs vs External IDs (`idx`)\n\nEvery resource has a Sona-generated UUID (`id`). Most resources also accept an **external identifier** (`idx` or `external_id`) that you control. This lets you reference resources using your own system's IDs without storing Sona's UUIDs.\n\nWhere an endpoint accepts `{user_id_or_idx}` in the path, you can pass either. For org unit references in request bodies, pass `{ \"id\": \"<sona_id>\" }` or `{ \"idx\": \"<your_external_id>\" }`.\n\nWe strongly recommend setting `external_id` / `idx` on every resource you create — it simplifies lookups and avoids the need to maintain an ID mapping table.\n\n### Pagination\n\nList endpoints return paginated results. Use `page` (1-indexed, not `page_number`) and `page_size` query parameters. The default page size is 10. Responses include `page_number`, `page_size`, `total_pages`, and `total_entries` alongside the `entries` array.\n\n### Errors\n\nThe API returns two error shapes. **Non-validation errors** (400, 401, 403, 404, 409, 500) return `{ \"error\": \"Not Found\", \"message\": \"...\" }`. **Validation errors** (422) on create/update usually return `{ \"errors\": { \"field_name\": [\"error message\", ...] } }` with per-field error arrays, but some 422s (a request that is well-formed but not allowed, or invalid query parameters) use the `{ \"error\", \"message\" }` shape instead. Each endpoint's 422 response names the schema it returns; when handling a 422 generically, check for `errors` first and fall back to `message`. The metrics endpoints are an exception: their errors are `{ \"error\": \"...\" }` with no `message`.\n\nEvery endpoint can return `401` (missing or invalid token) and `403` (the token is restricted to other endpoints) before the request is processed.\n\n### Date and Time Formats\n\nThe API uses **ISO 8601** throughout:\n\n- **Datetimes** are UTC with format `YYYY-MM-DDTHH:mm:ssZ` — e.g. `2025-03-15T14:30:00Z`. Always include the trailing `Z` (UTC timezone designator). Datetimes without a timezone are rejected.\n- **Dates** use format `YYYY-MM-DD` — e.g. `2025-03-15`. Used for fields like `start_date`, `finish_date`, and `final_date_of_employment`.\n\nAll timestamps returned by the API (`inserted_at`, `updated_at`, shift times, etc.) are in UTC. They end in `Z`, with one exception: `inserted_at` and `updated_at` on org attributes and org unit attributes are UTC but have no designator (e.g. `2025-03-15T14:30:00`). Treat a datetime without a designator as UTC.\n\n### Incremental Sync\n\nIf you are building an ongoing sync rather than a one-off import, use the `updated_at` query parameter on list endpoints to fetch only records that have changed since your last sync. Currently supported on:\n\n- `GET /api/v1/users?updated_at=2025-03-15T10:00:00Z`\n- `GET /api/v1/employment_periods?updated_at=2025-03-15T10:00:00Z`\n- `GET /api/v1/services?updated_at=2025-03-15T10:00:00Z` (a service counts as changed when the service itself, its config or one of its demand periods was updated, or when anything was added to its audit log, which covers removed demand periods)\n- `GET /api/v1/people_we_support?updated_at=2025-03-15T10:00:00Z` (the person's own record; status and care package changes do not count)\n- `GET /api/v1/care_packages?updated_at=2025-03-15T10:00:00Z` (also on `GET /api/v1/people_we_support/{id}/care_packages`). A package counts as updated when it changes, including being ended by a newer package, or when any of its hour lines gets a new approval status. This is the way to sync approvals: each package comes back with its lines' current statuses, so you don't need a call per hour line.\n- `GET /api/v1/status_records?updated_at=2025-03-15T10:00:00Z` (also on `GET /api/v1/people_we_support/{id}/status_records`). Every person's active/inactive history; a record counts as updated when it is created or changed, including being ended by a newer record.\n\nThis returns all records updated on or after the given timestamp. Store the timestamp of each successful sync and pass it on the next run to avoid re-processing unchanged records. Combine with pagination to handle large result sets.\n\n### Reference Data\n\nSeveral resources act as configuration that must exist before you can create users or employment periods. These are typically set up by the Sona team during onboarding, but can be retrieved via the API:\n\n| Resource | Endpoint | Used by |\n|----------|----------|---------|\n| Org Units | `GET /api/v1/org_units` | Users, Employment Periods |\n| Levels | `GET /api/v1/levels` | Employment Periods |\n| Contract Types | `GET /api/v1/contract_types` | Employment Periods (via Employment Specs) |\n| Jobs | `GET /api/v1/jobs` | Employment Periods |\n| Permission Roles | `GET /api/v1/permission_roles` | Employment Periods |\n| Shift Types | `GET /api/v1/shift_types` | Shifts |\n| Absence Types | `GET /api/v1/absence_types` | Absences |\n| Termination Reasons | `GET /api/v1/termination_reasons` | Employment Periods (termination) |\n| Metrics | `GET /api/v1/metrics` | Metric Values |\n\nFetch these first to obtain the IDs you will need when creating users and employment periods.\n\n### User lookup\n\n`GET /api/v1/users/lookup` resolves **one** user from **one** identity parameter (`insurance_number`, `employee_number`, `external_id`, `contact_email`, or `email`). The response includes that user, their employment periods (up to 200), and which parameter was used (`lookup.by`). See **Responses** on the endpoint for status codes.\n\n---\n\n## Common Workflows\n\n### Creating a User with an Employment Period\n\nA **User** represents a person in the system. An **Employment Period** represents a specific period of employment for that user (start date, contract type, org unit, etc.). Most integrations create both together.\n\n**Prerequisites:** Before creating a user you will need an org unit ID (from `GET /api/v1/org_units`). If creating an employment period, you will also need IDs for the level, contract type, and optionally the job — see the Reference Data table above.\n\n**Option A — Single request with `create_employment_period`**\n\nSet `create_employment_period: true` on `POST /api/v1/users` to create the user and a minimal employment period in one call. You must also supply `administrative_org_unit`. Optionally pass `employment_period_params` to configure the employment period (start date, employee number, level, etc.).\n\n**Important:** This option places the user into the Sona **onboarding flow**. Your Sona instance must have onboarding configured for this to work correctly. If your organisation does not use Sona's onboarding feature, use Option B instead.\n\n**Option B — Two separate requests**\n\n1. `POST /api/v1/users` — create the user (returns a `user_id`).\n2. `POST /api/v1/employment_periods` — create the employment period, referencing the `user_id` from step 1. This requires `user_id`, `start_date`, and an `employment_specs` array containing at least one entry with `contract_types`, `rates_of_pay`, and `contracted_hours`.\n\nUse Option B when you need full control over the employment period configuration or when your Sona instance does not have onboarding enabled.\n\n**Updating a user**\n\n`PUT /api/v1/users/{user_id}` updates user fields. Only the fields you include in the request body are changed — omitted fields are left untouched.\n\n**Granting manager access**\n\nTo make a user a manager, grant access to specific org units via `manager_org_units` (full read/write) or `read_only_org_units` (read only). A user also needs a **base role** on `user.roles` to be able to log in to the manager desktop — if you grant manager access without passing `base_roles`, Sona will automatically assign `manager` (or `read_only_manager` if only read-only access is granted) so the user can log in. Existing roles are never overwritten. Pass `base_roles` explicitly to override the default or to assign a more specific role (e.g. `hr_manager`, `payroll_manager`).\n\n---\n\n### Terminating and Undoing Termination\n\n**Prerequisites:** Termination requires the **leaver workflow** to be enabled on your Sona instance. If it is not enabled, the terminate endpoint will return a 422 error. Contact your Sona representative to enable it. If your organisation uses termination reasons, retrieve available reasons from your Sona configuration beforehand.\n\n**Terminating a user**\n\n`PUT /api/v1/users/{user_id_or_idx}/terminate` ends a user's employment. You can identify the user by their Sona ID or external ID. The only required field is `final_date_of_employment`; you can optionally provide a termination reason and details. See the endpoint documentation for accepted parameters.\n\nAfter termination the user's `state` transitions through: `active` → `leaver` (between the request and the final date) → `left` (after the final date). Shifts beyond the final date are automatically removed.\n\n**Undoing a termination**\n\nIf a termination was made in error, reverse it with `PUT /api/v1/users/{user_id_or_idx}/undo_termination`. No request body is required — the user's state is restored to `active` and the final date of employment is cleared.\n\n---\n\n### Rehiring a former employee\n\n**Prerequisites:** A **completed** previous employment period with a **finish date** must already exist in your organisation. You need **non-continuous employment periods** enabled if the **same employee number** has a **gap** between the old period’s end and the new period’s start. Otherwise those periods must be **continuous** (back-to-back). Contact Sona to confirm this setting if you see validation errors about continuity or about starting a new period after a termination.\n\nA **rehire** is a **new** employment period for someone whose **previous** period **ended** with a **finish date**, and who is **no longer active** with **`final_date_of_employment`** set and state **`left`**. You use a rehire when the person returns after a real exit. **`undo_termination`** corrects a mistaken termination while the employee is still a **leaver**, but **has not left** yet. **Do not** use **`undo_termination`** for a genuine return after employment has ended. **Do** create a new employment period.\n\n**`termination_would_rehire`:** On the **previous** employment period, if this has been set to **`false`**, it means the person is **not suitable for rehire** (do-not-rehire). The Client API does **not** reject a new `POST /api/v1/employment_periods` when this value is **`false`**. You must enforce do-not-rehire in your integration if your policy requires it.\n\n**Creating the new period**\n\nCall `POST /api/v1/employment_periods` for an **existing** user.\n\nSona **derives** **`is_rehire`** on the new period unless you set it yourself. It is **true** only when **all** of the following hold: at least one **other** employment period is **finished**; **`final_date_of_employment`** is set; the user is **`leaver`** or **`left`** (see **Terminating a user** above); and the **new** period’s **`finish_date`** is **unset** or **strictly after** today. If **`finish_date`** on the new period is **today** or **earlier**, **`is_rehire`** is not derived as true, so rehire cleanup does not run—**`final_date_of_employment`** stays set and the user is not reactivated.\n\n**What happens when `is_rehire` is true after create**\n\n**`forces_new_holiday_breakdown`** defaults to **`true`** unless you set it (see the employment period parameters in this spec). Sona clears **`final_date_of_employment`**. Sona sets the user’s state to **`active`**.\n\n**Payroll note:** A termination and rehire **within the same pay period** can require special handling in Sona (for example pay or average-pay splits). If you use integrated payroll and hit edge cases, work with Sona to align behaviour.\n\n---\n\n### Custom Forms and Entries\n\n**Custom Forms** define the structure (fields, validation rules) for collecting additional data about users — for example employee documents, onboarding checklists, or qualifications. **Custom Form Entries** are submitted instances of those forms.\n\n**Prerequisites:** Custom forms are configured by the Sona team for your organisation. The form structure — including field names, accepted values, required fields, and validation rules — is defined during this setup and cannot be changed via the API. Contact your Sona representative if you need a new form or changes to an existing one.\n\n**Step 1 — Discover available forms**\n\n`GET /api/v1/custom_forms` returns all custom forms configured for your organisation. Each form has a unique `id` and describes the fields an entry must include. Use this to determine the `custom_form_id` and the exact field names you need to supply when creating entries.\n\n**Step 2 — Create an entry**\n\n`POST /api/v1/custom_form_entries` with `custom_form_id`, `user_id`, and the field values defined by the form. The required and optional fields vary per form — refer to the form definition from Step 1. File fields can be uploaded via multipart form-data or as base64-encoded JSON (see the endpoint documentation for upload formats and size limits).\n\n**Step 3 — View entries**\n\n`GET /api/v1/custom_form_entries` lists all entries (paginated). Use `GET /api/v1/custom_form_entries/{id}` to retrieve a single entry.\n\n---\n\n## Webhooks\n\nSona supports real-time webhook notifications for 25 event types covering users, employment, absences, shifts, amendments, jobs, and agency shifts.\n\n**[View the full Webhooks Guide](webhooks/)** for delivery format, HMAC-SHA256 signature verification, all event types, and payload schemas.\n\nTo manage subscriptions, see the **Webhook Subscriptions** endpoints in the sidebar.\n","title":"Sona Client API","version":"1.14","x-logo":{"altText":"Sona logo","backgroundColor":"#FFFFFF","url":"https://api.sona.is/images/sona-logo-small.png"}},"paths":{"/api/v1/absence_types":{"get":{"description":"AbsenceType","operationId":"BackendWeb.ApiV1.AbsenceTypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/AbsenceTypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of AbsenceTypes","tags":["AbsenceTypes"]}},"/api/v1/absence_types/{absence_type_id}":{"get":{"description":"Get an individual absence_type. See the absence_type listing endpoint for a description of absence_types","operationId":"BackendWeb.ApiV1.AbsenceTypesController.show","parameters":[{"description":"Sona unique identifier of the absence_type","in":"path","name":"absence_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AbsenceType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual absence_type","tags":["AbsenceTypes"]}},"/api/v1/absence_types/{absence_type_id}/absence_subtypes":{"get":{"description":"AbsenceSubtype","operationId":"BackendWeb.ApiV1.AbsenceTypes.AbsenceSubtypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Absence Type ID (UUID)","in":"path","name":"absence_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/AbsenceSubtypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Absence Subtypes for a given Absence Type","tags":["AbsenceSubtypes"]}},"/api/v1/absence_types/{absence_type_id}/absence_subtypes/{id}":{"get":{"description":"Get an individual AbsenceSubtype. See the Absence Subtype listing endpoint for a description of Absence Subtypes","operationId":"BackendWeb.ApiV1.AbsenceTypes.AbsenceSubtypesController.show","parameters":[{"description":"Absence Type ID (UUID)","in":"path","name":"absence_type_id","required":true,"type":"string"},{"description":"Absence Subtype ID (UUID)","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AbsenceSubtype"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual Absence Subtype","tags":["AbsenceSubtypes"]}},"/api/v1/absence_types/{absence_type_id}/pay_variants":{"get":{"description":"Lists all pay variants associated with an absence type.","operationId":"BackendWeb.ApiV1.AbsenceTypes.PayVariantsController.index","parameters":[{"description":"Sona unique identifier of the absence type","in":"path","name":"absence_type_id","required":true,"type":"string"},{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayVariants"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The absence type ID is not a valid UUID, or the pagination parameters are invalid","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Paginated list of pay variants","tags":["PayVariants"]}},"/api/v1/absence_years":{"post":{"description":"Create a new absence year","operationId":"BackendWeb.ApiV1.AbsenceYearsController.create","parameters":[{"description":"The absence year parameters","in":"body","name":"absence_years","required":false,"schema":{"$ref":"#/definitions/CreateAbsenceYearParameters"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/AbsenceYear"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity. A changeset error, such as an overlapping absence year, returns `errors`; a refused `contract_id` or a start date without employment returns `error` and `message`.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new absence year","tags":["AbsenceYears"]}},"/api/v1/absences":{"get":{"description":"List all absences for the current organisation.","operationId":"BackendWeb.ApiV1.AbsencesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by user id. If omitted all absences are returned.","in":"query","name":"user_id","required":false,"type":"string"},{"description":"Filter by external user id. If omitted all absences are returned.","in":"query","name":"user_idx","required":false,"type":"string"},{"description":"Filter by absence type id. If omitted all absences are returned.","in":"query","name":"absence_type_id","required":false,"type":"string"},{"description":"order by starts at DESC if provided starts_at_desc","in":"query","name":"order_by","required":false,"type":"string"},{"description":"When true, each linked/unlinked shift will include a payroll_run_rows array with the raw payroll run row data (start_datetime, end_datetime, duration_in_minutes, entry_type, pay_type, variant). Use this when you need the payroll-authoritative datetimes rather than the scheduled shift times.","in":"query","name":"include_payroll_run_rows","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Absences"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"`user_id` or `absence_type_id` is not a valid UUID, or `order_by` is not a supported value","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of absences","tags":["Absences"]},"post":{"description":"Create a new absence","operationId":"BackendWeb.ApiV1.AbsencesController.create","parameters":[{"description":"The absence parameters","in":"body","name":"absence","required":false,"schema":{"$ref":"#/definitions/CreateAbsenceParameters"}}],"responses":{"200":{"description":"Success, absence created","schema":{"$ref":"#/definitions/CreateAbsenceResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to create an absence with these parameters, e.g. the absence type is managed in Sona rather than externally, or the end date is before a shift approved for payroll. Usually an `ErrorResponse`; field-level validation failures return `ValidationErrors` instead","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new absence","tags":["Absences"]}},"/api/v1/absences/{absence_period_id_or_idx}":{"delete":{"description":"Delete an absence","operationId":"BackendWeb.ApiV1.AbsencesController.delete","parameters":[{"description":"The Sona unique identifier or external id of the absence","in":"path","name":"absence_period_id_or_idx","required":true,"type":"string"},{"description":"Optional parameters for deletion behavior","in":"body","name":"delete_associated_shifts","required":false,"schema":{"properties":{"delete_associated_shifts":{"default":false,"description":"If true, also deletes shifts associated with this absence period.","type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"Success, absence deleted","schema":{"$ref":"#/definitions/DeleteAbsenceResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Absence not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"SSP is being recalculated for this employee; retry shortly","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The absence period has been approved for payroll, or one or more of its shifts are locked for payroll","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete an absence","tags":["Absences"]},"get":{"description":"Show an absence by id or idx","operationId":"BackendWeb.ApiV1.AbsencesController.show","parameters":[{"description":"The Sona unique identifier or external id of the absence","in":"path","name":"absence_period_id_or_idx","required":true,"type":"string"},{"description":"When true, each linked/unlinked shift will include a payroll_run_rows array with the raw payroll run row data (start_datetime, end_datetime, duration_in_minutes, entry_type, pay_type, variant). Use this when you need the payroll-authoritative datetimes rather than the scheduled shift times.","in":"query","name":"include_payroll_run_rows","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShowAbsenceResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Show an absence","tags":["Absences"]},"put":{"description":"Update an absence","operationId":"BackendWeb.ApiV1.AbsencesController.update","parameters":[{"description":"The Sona unique identifier or external id of the absence","in":"path","name":"absence_period_id_or_idx","required":true,"type":"string"},{"description":"The absence parameters","in":"body","name":"absence","required":false,"schema":{"$ref":"#/definitions/UpdateAbsenceParameters"}}],"responses":{"200":{"description":"Success, absence updated","schema":{"$ref":"#/definitions/UpdateAbsenceResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The absence period or its user could not be found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to update the absence. Rejections such as an absence type managed in Sona rather than externally, or a new end date before a shift approved for payroll, return `{error, message}`; field-level validation failures return `{errors}`","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an absence","tags":["Absences"]}},"/api/v1/absences/{absence_period_id_or_idx}/amend":{"post":{"description":"Amends an absence period","operationId":"BackendWeb.ApiV1.AbsencesController.amend","parameters":[{"description":"The Sona unique identifier or external id of the absence","in":"path","name":"absence_period_id_or_idx","required":true,"type":"string"},{"description":"The amendment absence parameters","in":"body","name":"absence","required":false,"schema":{"$ref":"#/definitions/AmendmentAbsenceParameters"}}],"responses":{"200":{"description":"Success: absence period amendment","schema":{"$ref":"#/definitions/ShowAmendmentResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Absence period not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The amendment was rejected, e.g. the associated shifts have changed or ends_at is before a payrolled shift. Invalid request fields return `ValidationErrors` instead","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Amend an absence period","tags":["Absences"]}},"/api/v1/agencies":{"get":{"description":"An agency in Sona is a third party business used to provide staff who can be assigned to shifts. Agencies can be created via the Sona UI or dynamically when using the shifts#assign_to_agency Endpoint. An agency worker is simply a user with a link to an agency, these are returned by the users#index endpoint with their `agency_id` set to the value of an agency. Currently a user can only be associated with one agency.","operationId":"BackendWeb.ApiV1.AgenciesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Agencies"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Agencies","tags":["Agencies"]}},"/api/v1/agencies/{agency_id}":{"get":{"description":"Returns information about a single agency. See the agency listing endpoint for a description Agencies.","operationId":"BackendWeb.ApiV1.AgenciesController.show","parameters":[{"description":"Sona unique identifier of the agency","in":"path","name":"agency_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Agency"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual agency","tags":["Agencies"]}},"/api/v1/annual_leave_requests":{"get":{"description":"Returns a list of annual leave requests sorted by creation date. Allows filtering by `user_id` and start and end dates.","operationId":"BackendWeb.ApiV1.AnnualLeaveRequestsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The user ID to filter by","in":"query","name":"user_id","required":false,"type":"string"},{"description":"Returns annual leave requests starting on or after the given date","in":"query","name":"min_start_date","required":false,"type":"string"},{"description":"Returns annual leave requests ending on or before the given date","in":"query","name":"max_end_date","required":false,"type":"string"},{"description":"The external ID to filter by","in":"query","name":"idx","required":false,"type":"string"},{"description":"The source type of the external ID to filter by","in":"query","name":"source_type","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"items":{"$ref":"#/definitions/AnnualLeaveRequest"},"type":"array"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"A query parameter is invalid: `user_id` is not a UUID, `page`/`page_size` is not an integer, `min_start_date`/`max_end_date` is not a date, or `state`/`source_type` is not an allowed value","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of annual leave requests","tags":["AnnualLeaveRequests"]}},"/api/v1/annual_leave_requests/{annual_leave_request_id}":{"get":{"description":"Returns an annual leave request by ID","operationId":"BackendWeb.ApiV1.AnnualLeaveRequestsController.show","parameters":[{"description":"The ID of the annual leave request to get","in":"path","name":"annual_leave_request_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AnnualLeaveRequest"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an annual leave request by ID","tags":["AnnualLeaveRequests"]}},"/api/v1/care_packages":{"get":{"description":"Newest first. Requires demand-based rostering to be enabled for your organisation (403 otherwise). `updated_at` counts a change to the package (including being superseded) or a new approval status on any of its hour lines.\n","operationId":"BackendWeb.ApiV1.CarePackagesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Only this person's packages (Sona id)","in":"query","name":"pws_id","required":false,"type":"string"},{"description":"Only this person's packages (their `upload_id`)","in":"query","name":"pws_idx","required":false,"type":"string"},{"description":"Exact match on the package code","in":"query","name":"idx","required":false,"type":"string"},{"description":"`true` only packages in force today (started, and not ended; UTC date), `false` only the others: ended, or starting later","in":"query","name":"current","required":false,"type":"boolean"},{"description":"Packages starting on or after this date (YYYY-MM-DD)","in":"query","name":"start_date_from","required":false,"type":"string"},{"description":"Packages starting on or before this date (YYYY-MM-DD)","in":"query","name":"start_date_to","required":false,"type":"string"},{"description":"Only packages changed on or after this UTC datetime (ISO 8601, trailing Z)","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/CarePackageDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List care packages","tags":["CarePackages"]}},"/api/v1/care_packages/{care_package_id}":{"get":{"description":"One package with its hour lines. Use the Sona id, or the package code (`idx`) with `is_idx=true`. `status` is the package's overall approval state (`pending`, `pre_approved`, `approved`, `rejected` or `partially_approved`), worked out from the latest status of each hour line. Hour lines are returned in the order they were given, except on packages created before 23 October 2026: that order wasn't recorded, so their lines come back in a fixed order that may differ from it.","operationId":"BackendWeb.ApiV1.CarePackagesController.show","parameters":[{"description":"Sona id, or `idx` when `is_idx=true`","in":"path","name":"care_package_id","required":true,"type":"string"},{"description":"Set to true to look the package up by `idx`","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CarePackageData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a care package","tags":["CarePackages"]}},"/api/v1/care_packages/{care_package_id}/hours/{hour_id}/statuses":{"get":{"description":"The line's approval history, newest first (by `effective_date`, then when it was added). The first entry is the line's current status. Requires demand-based rostering to be enabled for your organisation (403 otherwise).","operationId":"BackendWeb.ApiV1.CarePackageStatusesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Sona id, or the package code when `is_idx=true`","in":"path","name":"care_package_id","required":true,"type":"string"},{"description":"The hour line's Sona id, from the care package","in":"path","name":"hour_id","required":true,"type":"string"},{"description":"Set to true to look the package up by its code (`idx`)","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/HourStatusDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List an hour line's approval statuses","tags":["CarePackages"]},"post":{"description":"Appends an approval status to one hour line. A status is **added, never edited**, and the latest one is the line's current approval. There is no workflow: any status may follow any other. `effective_date` defaults to today, the UTC date as in the admin panel; it can't be in the future, before the package starts, or before the line's current status (a status is added, never back-dated). A new line starts as `pending`. The package's overall status updates from its lines. Once every line is `rejected` the package is withdrawn, as in the admin panel: it no longer counts towards demand and takes no more statuses (409); create a new package instead.\n","operationId":"BackendWeb.ApiV1.CarePackageStatusesController.create","parameters":[{"description":"Sona id, or the package code when `is_idx=true`","in":"path","name":"care_package_id","required":true,"type":"string"},{"description":"The hour line's Sona id, from the care package","in":"path","name":"hour_id","required":true,"type":"string"},{"description":"Set to true to look the package up by its code (`idx`)","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The status","in":"body","name":"status","required":true,"schema":{"$ref":"#/definitions/HourStatusCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/HourStatusData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"The package is rejected","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Add an approval status to an hour line","tags":["CarePackages"]}},"/api/v1/care_packages/{care_package_id}/statuses":{"post":{"description":"Applies one status to **every** hour line of the package in one call, all or nothing. The same date rules apply to each line, so if any line can't take the date, none changes. Returns one entry per line. Rejecting every line withdraws the package, and a withdrawn package takes no more statuses (409).","operationId":"BackendWeb.ApiV1.CarePackageStatusesController.create_for_package","parameters":[{"description":"Sona id, or the package code when `is_idx=true`","in":"path","name":"care_package_id","required":true,"type":"string"},{"description":"Set to true to look the package up by its code (`idx`)","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The status","in":"body","name":"status","required":true,"schema":{"$ref":"#/definitions/HourStatusCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/PackageStatusCreated"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"The package is rejected","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Add the same approval status to every hour line of a package","tags":["CarePackages"]}},"/api/v1/contract_types":{"get":{"description":"Contract type","operationId":"BackendWeb.ApiV1.ContractTypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ContractTypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Contract Types","tags":["ContractTypes"]}},"/api/v1/contract_types/{contract_type_id}":{"get":{"description":"Get an individual contract type. See the contract type listing endpoint for a description of contract types","operationId":"BackendWeb.ApiV1.ContractTypesController.show","parameters":[{"description":"Sona unique identifier of the contract_type","in":"path","name":"contract_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ContractType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual contract type","tags":["ContractTypes"]}},"/api/v1/contracted_hours":{"get":{"description":"Contracted Hours","operationId":"BackendWeb.ApiV1.ContractedHoursController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The external id to filter by","in":"query","name":"idx","required":false,"type":"string"},{"description":"The start_date to filter by. Expecting YYYY-MM-DD format.","in":"query","name":"start_date","required":false,"type":"string"},{"description":"The finish_date to filter by. Expecting YYYY-MM-DD format.","in":"query","name":"finish_date","required":false,"type":"string"},{"description":"The user_id to filter by","in":"query","name":"user_id","required":false,"type":"string"},{"description":"The employment_period_id to filter results by","in":"query","name":"employment_period_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ContractedHours"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"`user_id` is not a valid UUID, or `type_of_work_id`/`org_unit_id` doesn't belong to the organisation","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of ContractedHours","tags":["ContractedHours"]},"post":{"description":"Create new contracted hours. See the ContractedHours listing endpoint for a description of a contracted hours. One of the employment_period_id or employment_period_idx parameters is required. The hours are bounded by the length of the period: at most 168 for `weekly`, 744 for `monthly` and `calendar_month_daily`, and 24 per day of `rolling_period_sequence_length_days` for `rolling_sequence`. Hours beyond that are rejected with a 422, on create and on any update that names `hours`, `period` or `rolling_period_sequence_length_days`. Contracted hours can't overlap other contracted hours on the same employment spec with the same `period` (for `rolling_sequence`, the same length) and the same or intersecting types of work and org units; an overlap is rejected with a 422 under `duplicate`. Creating open-ended hours (no `finish_date`) that start after open-ended hours of the same kind ends those hours the day before instead. Where the only overlap is the day one record ends and the next of the same kind starts, the earlier record is ended the day before instead.","operationId":"BackendWeb.ApiV1.ContractedHoursController.create","parameters":[{"description":"The contracted hours parameters","in":"body","name":"contracted_hours","required":false,"schema":{"$ref":"#/definitions/ContractedHoursCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ContractedHour"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The request is invalid. `message` is a string for request-level problems (e.g. a missing employment period id or idx, an org unit or type of work outside the organisation, hours beyond the period's bound) or an object mapping each invalid field to its error messages for validation failures","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create new contracted hours","tags":["ContractedHours"]}},"/api/v1/contracted_hours/{contracted_hours_id_or_idx}":{"delete":{"description":"Delete a future dated contracted hours record. Contracted hours that start today or earlier, in your organisation's timezone, cannot be deleted. When addressing the record by external id, start_date and type_of_work_id are required alongside is_idx. A token restricted to specific endpoints needs `DELETE /api/v1/contracted_hours/:id` in its list, or the response is a 403.","operationId":"BackendWeb.ApiV1.ContractedHoursController.delete","parameters":[{"description":"Sona unique identifier or external id of the contracted hours","in":"path","name":"contracted_hours_id_or_idx","required":true,"type":"string"},{"description":"IF contracted_hours_id_or_idx is an external id, this parameter should be set to true","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The date from when these contracted hours are applicable","format":"date","in":"query","name":"start_date","required":false,"type":"string"},{"description":"Sona unique identifier of the type of work condition to filter contracted hours by","in":"query","name":"type_of_work_id","required":false,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The contracted hours can't be deleted. `message` is a string, or an object mapping fields to error messages when the deletion fails validation","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete contracted hours","tags":["ContractedHours"]},"get":{"description":"Get individual contracted_hours. See the contracted_hours listing endpoint for a description of contracted_hours","operationId":"BackendWeb.ApiV1.ContractedHoursController.show","parameters":[{"description":"Sona unique identifier or external id of the contracted hours","in":"path","name":"contracted_hours_id_or_idx","required":true,"type":"string"},{"description":"IF contracted_hours_id_or_idx is an external id, this parameter should be set to true","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The date from when these contracted hours are applicable","format":"date","in":"query","name":"start_date","required":false,"type":"string"},{"description":"The date from when these contracted hours are no longer applicable","format":"date","in":"query","name":"finish_date","required":false,"type":"string"},{"description":"Sona unique identifier of the type of work condition to filter contracted hours by","format":"date","in":"query","name":"type_of_work_id","required":false,"type":"string"},{"description":"Sona unique identifier of the location condition to filter contracted hours by","format":"date","in":"query","name":"org_unit_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ContractedHour"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get individual contracted_hours","tags":["ContractedHours"]},"put":{"description":"Update an existing contracted hours. See the ContractedHours listing endpoint for a description of a contracted hours.  One of the id or idx parameters is required. If both employment_period_id and employment_period_idx are given employment_period_id has a higher precedence. The hours are bounded by the length of the period: at most 168 for `weekly`, 744 for `monthly` and `calendar_month_daily`, and 24 per day of `rolling_period_sequence_length_days` for `rolling_sequence`. Hours beyond that are rejected with a 422, on create and on any update that names `hours`, `period` or `rolling_period_sequence_length_days`. An update is rejected with a 422 under `duplicate` if it makes these hours overlap other contracted hours on the same employment spec with the same `period` (for `rolling_sequence`, the same length) and the same or intersecting types of work and org units. Where the only overlap is the day one record ends and the next of the same kind starts, the earlier record is ended the day before instead.","operationId":"BackendWeb.ApiV1.ContractedHoursController.update","parameters":[{"description":"Sona unique identifier or external id of the contracted hours","in":"path","name":"contracted_hours_id_or_idx","required":true,"type":"string"},{"description":"The contracted hours parameters","in":"body","name":"contracted_hours","required":false,"schema":{"$ref":"#/definitions/UpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ContractedHour"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The request is invalid. `message` is a string for request-level problems (e.g. an org unit or type of work outside the organisation, hours beyond the period's bound) or an object mapping each invalid field to its error messages for validation failures","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an existing contracted hours","tags":["ContractedHours"]}},"/api/v1/csv_imports/upload/{file_type}":{"post":{"consumes":["multipart/form-data"],"description":"Uploads a CSV file to the importer, and returns a report of running the import","operationId":"BackendWeb.ApiV1.CsvImportsController.upload","parameters":[{"description":"CSV file type","in":"path","name":"file_type","required":true,"type":"string"},{"description":"CSV file","in":"formData","name":"csv","required":true,"type":"file"}],"produces":["application/json"],"responses":{"200":{"description":"Success. The file was processed; row-level failures are reported in `results`, not as an error status.","schema":{"example":{"file_type":"users","results":[{"ok":{"id":"5c7265ad-440a-4d20-9b93-bfc287ee0909"}},{"error":{"email":["has already been taken"]}}]},"properties":{"file_type":{"description":"The CSV file type that was imported","type":"string"},"results":{"description":"One entry per imported record: `{\"ok\": ...}` with the imported record, or `{\"error\": ...}` with field errors (field name to messages) or a `type`/`message` object","items":{"type":"object"},"type":"array"}},"type":"object"}},"400":{"description":"Bad request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Upload a CSV file to the importer","tags":["CsvImports"]}},"/api/v1/custom_form_entries":{"get":{"description":"Custom Form Entries","operationId":"BackendWeb.ApiV1.CustomFormEntriesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/CustomFormEntries"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of CustomFormEntries","tags":["CustomFormEntries"]},"post":{"consumes":["application/json","multipart/form-data"],"description":"Create a new custom form entry.\n\n**Step-by-step workflow:**\n\n1. **Fetch the custom form** — call `GET /api/v1/custom_forms/{custom_form_id}` to retrieve the form definition.\n2. **Check `submission_type`** — this determines which fields are required:\n   - `single` — send `custom_form_id`, `user_id`, and every field from the form's single fieldset.\n   - `multiple` — the form has several fieldsets, one per document type. Pick the document type you need, then send `custom_form_id`, `user_id`, `employee_document_type`, and every field from the matching fieldset.\n3. **Find the field keys** — look at the `schema.fieldsets[].fields[].name` values in the custom form response. These are the keys to include in your create request.\n4. **For `multiple` forms, find valid document types** — each fieldset (after the first) has a `variation.value` (e.g. `\"CV\"`, `\"Passport\"`). Pass the chosen value as `employee_document_type`.\n\n**Important — field names must be exact:** any parameter that does not match a field `name` from the custom form's schema is silently ignored. If your file upload or other fields appear as `null` in the response `data` object, check that the parameter names in your request exactly match the field names from `GET /api/v1/custom_forms/{id}`. There is no `file`, `document`, or `type` parameter — the file content must be the value of the field's own name (e.g. `document_1`).\n\n**File uploads** can be sent in three ways:\n\n1. `multipart/form-data` with a `Plug.Upload` file field (standard browser-style upload)\n2. `application/json` with a data URL string: `\"data:application/pdf;base64,JVBERi0xLjQ...\"`\n3. `application/json` with a JSON object: `{\"base64\": \"JVBERi0xLjQ...\", \"filename\": \"doc.pdf\", \"content_type\": \"application/pdf\"}`\n   - `base64` is required\n   - At least one of `filename` or `content_type` must be provided\n   - If `content_type` is omitted it is inferred from the filename extension\n\n**Base64 upload limits:**\n- Maximum file size: 30 MB (after decoding)\n- Allowed content types: PDF, PNG, JPEG, GIF, WebP, SVG, Word, Excel, PowerPoint, plain text, CSV, RTF\n- Maximum JSON request body: 60 MB by default (all fields combined, after base64 encoding)\n- Note: a 30 MB file is ~40 MB when base64-encoded. For large files, prefer multipart uploads.\n","operationId":"BackendWeb.ApiV1.CustomFormEntriesController.create","parameters":[{"description":"The parameters for the new custom form entry. `custom_form_id` and `user_id` are always required. Additional required or optional fields are defined by the custom form the entry is being created for.","in":"body","name":"custom_form_entries","required":false,"schema":{"$ref":"#/definitions/CreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CustomFormEntry"}},"401":{"description":"Not Authorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity. `message` is a string (e.g. missing fields, wrong document type, failed upload), or an object of field errors when validation fails.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create new custom form entry","tags":["CustomFormEntries"]}},"/api/v1/custom_form_entries/{custom_form_entry_id}":{"get":{"description":"Get an individual custom_form_entry. See the custom_form_entries listing endpoint for a description of custom_form_entries","operationId":"BackendWeb.ApiV1.CustomFormEntriesController.show","parameters":[{"description":"Sona unique identifier of the custom_form_entry","in":"path","name":"custom_form_entry_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CustomFormEntry"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get individual custom_form_entries","tags":["CustomFormEntries"]}},"/api/v1/custom_form_entries/{id}":{"delete":{"description":"Permanently delete a custom form entry. **This action cannot be undone.**\n\n**⚠ Important — deleting entries has cascading effects across the system:**\n\nCustom form entries are referenced by multiple Sona subsystems. Deleting an entry\nwill have the following consequences:\n\n- **Tasks** — Any incomplete tasks linked to this entry will be **automatically marked\n  as completed**. This is irreversible and may confuse users who see tasks disappear\n  from their task list without explanation.\n- **Payroll** — Entries that have been submitted to payroll (i.e. have linked payroll\n  additional payment/deduction elements) **cannot be deleted**. The request will return\n  a `422` error. This is enforced by a database constraint to prevent orphaned payroll\n  records.\n- **Approval workflows** — Entries linked to any approval record (pending, approved, or\n  rejected) **cannot be deleted**. The request will return a `422` error.\n- **Locked entries** — Entries that have been locked (e.g. by an approval or payroll\n  workflow) **cannot be deleted**. The request will return a `422` error.\n- **Shifts** — If the entry is linked to a shift via the shifts–custom-form-entries\n  association, deletion will leave the shift with a broken reference. The shift may no\n  longer display its associated custom form data correctly.\n- **Employee documents** — If the entry is linked to employee document versions, deletion\n  may orphan the document version records, causing missing data in the employee's\n  document history.\n- **Form requests** — If the entry was created via a form request (self-service or manager\n  submission), the form request record will retain a reference to the now-deleted entry,\n  which may cause errors when viewing the form request history.\n- **Data gathering steps** — If the entry was created as part of a data gathering workflow,\n  deletion will leave the step referencing a non-existent entry, potentially breaking the\n  data gathering flow.\n- **Workflows** — If the entry was produced by an automated workflow, the workflow output\n  record will retain a dangling reference. Re-running the workflow will not recreate the\n  deleted entry.\n- **HR reports** — Reports referencing the deleted entry's data will no longer include it,\n  which may change historical report outputs.\n\nAlways verify whether the entry is connected to other processes before deleting via the API.\nUse `GET /api/v1/custom_form_entries/{id}` to inspect the entry first.\n","operationId":"BackendWeb.ApiV1.CustomFormEntriesController.delete","parameters":[{"description":"Sona unique identifier of the custom form entry","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity - entry is locked, linked to an approval, or has been submitted to payroll","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete a custom form entry","tags":["CustomFormEntries"]},"put":{"consumes":["application/json"],"description":"Update an existing custom form entry. Only the fields provided in the request body\nwill be updated; omitted fields retain their current values.\n\nThe `custom_form_id` and `user_id` cannot be changed after creation.\n\n**⚠ Important — updating entries can affect other parts of the system:**\n\nCustom form entries may be referenced by other Sona subsystems. Updating field values\ncan have downstream consequences including:\n\n- **Payroll** — If the entry has been used to generate payroll additional payment/deduction\n  elements, updating field values will **not** retroactively update those payroll records.\n  This can cause discrepancies between the entry data and what was submitted to payroll.\n- **Approval workflows** — Entries linked to an approval (pending or completed) cannot be\n  updated. The request will return a `422` error.\n- **Shifts** — Entries linked to shifts (e.g. shift-specific custom form data) will reflect\n  the updated values. Ensure the new values are still valid in the context of the shift.\n- **Employee documents** — If the entry is linked to employee document versions, updating\n  file upload fields may leave stale references to previous document versions.\n- **Form requests** — Entries created via form requests (e.g. self-service or manager\n  submissions) are linked back to the form request record. Updating the entry directly\n  bypasses any form request validation or approval logic.\n- **Data gathering steps** — If the entry was created as part of a data gathering workflow,\n  updating it directly bypasses the step completion logic and may cause the step to appear\n  out of sync with its stored data.\n- **Workflows** — Entries produced by automated workflows are tracked as workflow outputs.\n  Updating these entries does not re-trigger or update the workflow.\n- **HR reports** — Reports that reference custom form entry data will reflect updated values\n  on next run, which may change historical report outputs.\n\nAlways verify whether the entry is connected to other processes before updating via the API.\n","operationId":"BackendWeb.ApiV1.CustomFormEntriesController.update","parameters":[{"description":"Sona unique identifier of the custom form entry","in":"path","name":"id","required":true,"type":"string"},{"description":"The fields to update on the custom form entry. Only provided fields are changed.","in":"body","name":"custom_form_entries","required":false,"schema":{"$ref":"#/definitions/UpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CustomFormEntry"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an existing custom form entry","tags":["CustomFormEntries"]}},"/api/v1/custom_forms":{"get":{"description":"Custom Forms","operationId":"BackendWeb.ApiV1.CustomFormsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/CustomForms"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of CustomForms","tags":["CustomForms"]}},"/api/v1/custom_forms/{custom_form_id}":{"get":{"description":"Get individual custom_form. See the custom_forms listing endpoint for a description of custom_forms","operationId":"BackendWeb.ApiV1.CustomFormsController.show","parameters":[{"description":"Sona unique identifier","in":"path","name":"custom_form_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CustomForm"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get individual custom_form","tags":["CustomForms"]}},"/api/v1/employment_periods":{"get":{"description":"Returns a paginated list of employment periods. Supports filtering by employee number, external identifier, user ID, or last updated timestamp.\n\n**Pagination**: Results are paginated. Use the `page` and `page_size` query parameters to navigate. The default page size is 10. Note: the parameter name is `page` (not `page_number`). The response includes `page_number`, `page_size`, `total_pages`, and `total_entries` metadata to assist with iteration. Example: `GET /api/v1/employment_periods?page=2&page_size=100`","operationId":"BackendWeb.ApiV1.EmploymentPeriodsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Unique number of the employee who is working this employment_period","in":"query","name":"employee_number","required":false,"type":"string"},{"description":"The idx (external identifier) to filter by","in":"query","name":"idx","required":false,"type":"string"},{"description":"The user_id to filter by","in":"query","name":"user_id","required":false,"type":"string"},{"description":"UTC datetime to filter employment periods updated on or after this timestamp. Use format YYYY-MM-DDThh:mm:ssZ in UTC (e.g. 2024-01-15T10:00:00Z)","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/EmploymentPeriodsIndex"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List of Employment Periods","tags":["EmploymentPeriods"]},"post":{"description":"Create a new employment period for a user.\n\n## Employment Specs\n\nAt least one employment spec must be provided, each containing `contract_types`, `rates_of_pay`, and `contracted_hours`.\n\n## Rates of Pay\n\nRates of pay within an employment spec support two modes:\n\n1. **Reference an existing rate**: provide `{\"id\": \"<uuid>\"}` to link a pre-existing rate of pay to the employment spec.\n2. **Create a new rate inline**: omit `id` and provide `amount`, `currency`, `start_date`, `pay_type` (and optionally `finish_date`, `idx`, `is_default`, `label`, `type_of_work_id`, `org_unit_id`) to create a new rate of pay as part of the employment period creation.\n\nBoth modes can be mixed in the same request. When both `id` and inline fields are provided, `id` takes precedence and the inline fields are silently ignored.\n\nWhen creating rates inline, set `is_default` to `true` if the rate should be treated as the default rate for holiday pay derivation (requires the default rates feature to be enabled).\n\nFor organisations with UK minimum wage validation enabled, an inline GBP rate below the apprentice-rate hard floor (£7.55, £8.00 from April 2026) is rejected with a 422 naming the required amount, and no employment period is created. This floor is not age-banded, so a rate that is legal for an apprentice but below the National Living Wage for the employee's age is still accepted. A salaried rate is judged on its hourly equivalent, derived from the `contracted_hours` in the same request. A currency that is not a recognised three-letter ISO 4217 code is also rejected with a 422.\n\nContracted hours are bounded by the length of the period: at most 168 hours for `weekly`, 744 for `monthly` and `calendar_month_daily`, and 24 per day of `rolling_period_sequence_length_days` for `rolling_sequence`. Hours beyond that are rejected with a 422, and no employment period is created.\n\n## Contract\n\nEvery employment period belongs to a contract, identified by `contract_id`.\n\n**Organisations without the overlapping employment periods feature** manage contracts automatically: the new period joins the person's existing contract (inherited from their latest employment period), or starts a new contract if it is their first period. Neither `contract_id` nor `starts_new_contract` may be passed.\n\n**Organisations with the feature enabled** must state the request's intent, one of:\n\n- `contract_id` (with `starts_new_contract` false or omitted): the new period continues that contract. The value must be the `contract_id` of one of the same person's existing employment periods.\n- `starts_new_contract: true`: the new period starts a new contract with an auto-generated `contract_id` — which must therefore not be passed.\n\nThe contract is fixed at creation and cannot be changed via the update endpoint.\n\nTwo more rules apply to organisations with the feature enabled:\n\n- Employment periods on different contracts whose dates overlap must share one `employee_number`. A different number is rejected with a 422 on `employee_number`. An employment period without an employee number neither constrains nor is constrained.\n- To continue a contract, first set a `finish_date` on that contract's current employment period with `PUT /api/v1/employment_periods/{employment_period_id}`, then create the new period with that `contract_id`. While the current employment period has no `finish_date`, a later one is rejected with a 422 on `employee_number`: \"Please, close the latest existing employment period before creating a new one.\" Another contract that is still running does not block it.\n\nTo end one contract while the others continue, set a `finish_date` on its current employment period. `PUT /api/v1/users/{user_id_or_idx}/terminate` ends every contract.\n","operationId":"BackendWeb.ApiV1.EmploymentPeriodsController.create","parameters":[{"description":"The employment_period parameters","in":"body","name":"employment_period","required":false,"schema":{"$ref":"#/definitions/EmploymentPeriodParameters"}}],"responses":{"200":{"description":"Success, employment_period created","schema":{"$ref":"#/definitions/EmploymentPeriod"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The user or another referenced resource was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to create an employment_period with these parameters. `message` is a string for rejections such as an open employment period already existing on the contract, or an object mapping field names to lists of messages for field validation failures (e.g. `{\"employment_specs\": [\"is required\"]}`).","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new employment_period","tags":["EmploymentPeriods"]}},"/api/v1/employment_periods/{employment_period_id_or_idx}":{"delete":{"description":"Delete a future dated employment period. Currently applying employment periods cannot be deleted.","operationId":"BackendWeb.ApiV1.EmploymentPeriodsController.delete","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Employment period not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The employment period can't be deleted, e.g. because it already applies. `message` is an object mapping the failing record to its field errors.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete an employment period","tags":["EmploymentPeriods"]}},"/api/v1/employment_periods/{employment_period_id}":{"put":{"description":"Update an individual employment period based on employment_period_id OR the external idx.\n\n## Rates of Pay\n\nRates of pay within an employment spec support two modes:\n\n1. **Reference an existing rate**: provide `{\"id\": \"<uuid>\"}` to link a pre-existing rate of pay to the employment spec.\n2. **Create a new rate inline**: omit `id` and provide `amount`, `currency`, `start_date`, `pay_type` (and optionally `finish_date`, `idx`, `is_default`, `label`, `type_of_work_id`, `org_unit_id`) to create a new rate of pay as part of the employment period update.\n\nBoth modes can be mixed in the same request. When both `id` and inline fields are provided, `id` takes precedence and the inline fields are silently ignored.\n\nWhen creating rates inline, set `is_default` to `true` if the rate should be treated as the default rate for holiday pay derivation (requires the default rates feature to be enabled).\n","operationId":"BackendWeb.ApiV1.EmploymentPeriodsController.update","parameters":[{"description":"Sona unique identifier of the employment_period","in":"path","name":"employment_period_id","required":true,"type":"string"},{"description":"External identifier of the employment_period","in":"query","name":"idx","required":false,"type":"string"},{"description":"The employment_period parameters","in":"body","name":"employment_period","required":false,"schema":{"$ref":"#/definitions/EmploymentPeriodParameters"}}],"responses":{"200":{"description":"Success, employment_period updated","schema":{"$ref":"#/definitions/EmploymentPeriod"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"An employment_period with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to update employment_period with these parameters. `message` is an object mapping field names to lists of messages for field validation failures, or a string for other rejections.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}}},"summary":"Update an individual employment_period","tags":["EmploymentPeriods"]}},"/api/v1/employment_specs/contract_type/bulk":{"post":{"description":"Update contract types for multiple employment specs.","operationId":"BackendWeb.ApiV1.EmploymentSpecsController.bulk_update_contract_type","parameters":[{"description":"Employment spec contract type updates","in":"body","name":"updates","required":false,"schema":{"$ref":"#/definitions/EmploymentSpecContractTypeBulkUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/EmploymentSpecContractTypeBulkUpdateResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Bulk update employment spec contract types","tags":["EmploymentSpecs"]}},"/api/v1/employment_specs/{employment_spec_id}/contract_type":{"put":{"description":"Update the contract type for an employment spec.","operationId":"BackendWeb.ApiV1.EmploymentSpecsController.update_contract_type","parameters":[{"description":"Sona unique identifier of the employment_spec","in":"path","name":"employment_spec_id","required":true,"type":"string"},{"description":"The employment spec contract type parameters","in":"body","name":"employment_spec","required":false,"schema":{"$ref":"#/definitions/EmploymentSpecContractTypeUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/EmploymentSpec"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The contract type change failed validation (`message` is an object of field names to error lists), or recalculating holiday allowances failed (`message` is a string).","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an employment spec contract type","tags":["EmploymentSpecs"]}},"/api/v1/external_mappings":{"get":{"description":"List external mappings for the current organisation","operationId":"BackendWeb.ApiV1.ExternalMappingsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by external identifier","in":"query","name":"idx","required":false,"type":"string"},{"description":"Filter by source type","in":"query","name":"source_type","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ExternalMappings"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of external mappings","tags":["ExternalMappings"]},"post":{"description":"Create a new external mapping","operationId":"BackendWeb.ApiV1.ExternalMappingsController.create","parameters":[{"description":"The external mapping parameters","in":"body","name":"external_mapping","required":false,"schema":{"$ref":"#/definitions/CreateExternalMappingParameters"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/ExternalMapping"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Field validation failed (`{errors}`), or an ID parameter is not a valid UUID (`{error, message}`, with `message` mapping the parameter to its error, e.g. `{\"user_id\": \"invalid format\"}`)","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new external mapping","tags":["ExternalMappings"]}},"/api/v1/external_mappings/{id}":{"get":{"description":"Get a single external mapping data.","operationId":"BackendWeb.ApiV1.ExternalMappingsController.show","parameters":[{"description":"The Sona unique identifier of the external mapping.","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ExternalMapping"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID. `message` maps the parameter to its error, e.g. `{\"id\": \"invalid format\"}`","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a single external mapping","tags":["ExternalMappings"]},"put":{"description":"Update an external mapping","operationId":"BackendWeb.ApiV1.ExternalMappingsController.update","parameters":[{"description":"Sona unique identifier of external mapping","in":"path","name":"id","required":true,"type":"string"},{"description":"The external mapping parameters","in":"body","name":"external_mapping","required":false,"schema":{"$ref":"#/definitions/UpdateExternalMappingParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ExternalMapping"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"External mapping not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Field validation failed (`{errors}`), or an ID parameter is not a valid UUID (`{error, message}`, with `message` mapping the parameter to its error, e.g. `{\"user_id\": \"invalid format\"}`)","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an external mapping","tags":["ExternalMappings"]}},"/api/v1/forecast_runs":{"post":{"description":"Records that a forecast run has started for the authenticated organisation. Returns the forecast run id which the caller must pass back to a terminal endpoint.","operationId":"BackendWeb.ApiV1.ForecastRunsController.start","parameters":[{"description":"The forecast run start parameters","in":"body","name":"forecast_run","required":false,"schema":{"$ref":"#/definitions/StartForecastRunParameters"}}],"responses":{"200":{"description":"Success, forecast run started","schema":{"$ref":"#/definitions/ForecastRunResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to start a forecast run with these parameters","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Record the start of a forecast run","tags":["ForecastRuns"]}},"/api/v1/forecast_runs/{id}/complete":{"patch":{"description":"Marks a previously started forecast run as succeeded. `train_wape` is optional at completion; use the train WAPE endpoint to backfill it later.","operationId":"BackendWeb.ApiV1.ForecastRunsController.complete","parameters":[{"description":"The forecast run id returned by the start endpoint","in":"path","name":"id","required":true,"type":"string"},{"description":"The forecast run completion parameters","in":"body","name":"forecast_run","required":false,"schema":{"$ref":"#/definitions/CompleteForecastRunParameters"}}],"responses":{"200":{"description":"Success, forecast run updated","schema":{"$ref":"#/definitions/ForecastRunResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Forecast run not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to complete the forecast run with these parameters","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Record the completion of a forecast run","tags":["ForecastRuns"]}},"/api/v1/forecast_runs/{id}/fail":{"patch":{"description":"Marks a previously started forecast run as failed and optionally records an error message.","operationId":"BackendWeb.ApiV1.ForecastRunsController.fail","parameters":[{"description":"The forecast run id returned by the start endpoint","in":"path","name":"id","required":true,"type":"string"},{"description":"The forecast run failure parameters","in":"body","name":"forecast_run","required":false,"schema":{"$ref":"#/definitions/FailForecastRunParameters"}}],"responses":{"200":{"description":"Success, forecast run updated","schema":{"$ref":"#/definitions/ForecastRunResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Forecast run not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to fail the forecast run with these parameters","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Record the failure of a forecast run","tags":["ForecastRuns"]}},"/api/v1/forecast_runs/{id}/train_wape":{"patch":{"description":"Updates the train WAPE for a succeeded forecast run without changing the completion timestamp.","operationId":"BackendWeb.ApiV1.ForecastRunsController.update_train_wape","parameters":[{"description":"The forecast run id returned by the start endpoint","in":"path","name":"id","required":true,"type":"string"},{"description":"The forecast run train WAPE parameters","in":"body","name":"forecast_run","required":false,"schema":{"$ref":"#/definitions/ForecastRunTrainWapeParameters"}}],"responses":{"200":{"description":"Success, forecast run updated","schema":{"$ref":"#/definitions/ForecastRunResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Forecast run not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to update the forecast run with these parameters","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Backfill forecast train WAPE","tags":["ForecastRuns"]}},"/api/v1/forecasts":{"get":{"description":"Returns forecast runs for the authenticated organisation. Results are sorted by `started_at` descending.","operationId":"BackendWeb.ApiV1.ForecastRunsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by forecast target","in":"query","name":"target","required":false,"type":"string"},{"description":"Filter to runs started on or after this ISO8601 timestamp","in":"query","name":"started_after","required":false,"type":"string"},{"description":"Filter to runs started on or before this ISO8601 timestamp","in":"query","name":"started_before","required":false,"type":"string"},{"description":"Filter by status","enum":["running","succeeded","failed"],"in":"query","name":"status","required":false,"type":"string"},{"description":"Filter by train WAPE presence","enum":["has","missing"],"in":"query","name":"train_wape","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ForecastRunsResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get forecast runs","tags":["ForecastRuns"]}},"/api/v1/forecasts/{id}":{"get":{"description":"","operationId":"BackendWeb.ApiV1.ForecastRunsController.show","parameters":[{"description":"The forecast run id","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ForecastRunResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Forecast run not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a forecast run","tags":["ForecastRuns"]}},"/api/v1/holiday_adjustments":{"get":{"description":"Returns a list of holiday adjustments sorted by creation date. Allows filtering by `user_id`.","operationId":"BackendWeb.ApiV1.HolidayAdjustmentsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The user ID to filter by","in":"query","name":"user_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/HolidayAdjustments"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a filter parameter is invalid; `{\"error\", \"message\"}` with `message` mapping parameter names to errors when pagination or sorting parameters are invalid.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of holiday adjustments","tags":["HolidayAdjustments"]},"post":{"description":"Create a holiday adjustment","operationId":"BackendWeb.ApiV1.HolidayAdjustmentsController.create","parameters":[{"description":"Holiday adjustment parameters","in":"body","name":"holiday_adjustments","required":false,"schema":{"$ref":"#/definitions/HolidayAdjustmentCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/HolidayAdjustment"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Employee not found","schema":{"$ref":"#/definitions/ValidationErrors"}},"409":{"description":"Holiday write in progress; retry after the Retry-After delay","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a holiday adjustment","tags":["HolidayAdjustments"]}},"/api/v1/holiday_balance/{user_id}":{"get":{"description":"Retrieve the holiday balance for a specific user.","operationId":"BackendWeb.ApiV1.HolidayBalanceController.show","parameters":[{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"},{"description":"The date for which to show holiday balance","format":"date","in":"query","name":"date","required":false,"type":"string"},{"description":"The Sona unique identifier of the contract the request is about, from `contract_id` on `GET /api/v1/employment_periods`. Only used by organisations with overlapping employment periods enabled. Required when the user holds employment periods on several contracts covering the requested date; without it the only contract covering that date is used. A contract whose employment periods have ended still reads its own absence year covering the date.","in":"query","name":"contract_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/HolidayBalance"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No holiday balance could be found (`{error, message}`), or the user was not found in the organisation (`{\"errors\": {\"user_id\": [\"not found\"]}}`).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a user's holiday balance","tags":["HolidayBalance"]}},"/api/v1/jobs":{"get":{"description":"A job represents a role or position within the organisation. Jobs can be archived when no longer in use. Use the archived query parameter to filter by active or archived jobs.","operationId":"BackendWeb.ApiV1.JobsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by whether the job has been archived or not. If omitted all jobs are returned.","in":"query","name":"archived","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Jobs"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid query parameters: `page` or `page_size` isn't an integer, or `archived` isn't a boolean","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of jobs","tags":["Jobs"]}},"/api/v1/jobs/{job_id}":{"get":{"description":"Get an individual job. See the job listing endpoint for a description of jobs.","operationId":"BackendWeb.ApiV1.JobsController.show","parameters":[{"description":"Sona unique identifier of the job","in":"path","name":"job_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Job"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual job","tags":["Jobs"]}},"/api/v1/levels":{"get":{"description":"Levels represent a hierarchical classification within an organisation, such as seniority bands or pay grades. Levels can be nested via a parent reference to form a tree structure.","operationId":"BackendWeb.ApiV1.LevelsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Levels"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of levels","tags":["Levels"]}},"/api/v1/levels/{level_id}":{"get":{"description":"Get an individual level. See the level listing endpoint for a description of levels","operationId":"BackendWeb.ApiV1.LevelsController.show","parameters":[{"description":"Sona unique identifier of the level","in":"path","name":"level_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Level"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual level","tags":["Levels"]}},"/api/v1/locked_periods":{"get":{"description":"Returns locked periods for the organisation, optionally filtered by date range and/or location. A locked period represents a time range during which the roster for a location is locked and no further changes can be made. Requires the period locking feature to be enabled for the organisation.","operationId":"BackendWeb.ApiV1.LockedPeriodsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Start of the date range to filter by (inclusive). in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"start_datetime","required":false,"type":"string"},{"description":"End of the date range to filter by (inclusive). in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"end_datetime","required":false,"type":"string"},{"description":"Filter to specific location (org unit) IDs. When omitted, all locations are returned.","in":"query","items":{"type":"string"},"name":"location_ids","required":false,"type":"array"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/LockedPeriods"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of locked periods","tags":["LockedPeriods"]}},"/api/v1/metric_values":{"get":{"description":"Read recorded metric values — actuals, forecasts and budgets — for the organisation.\n\n**Pagination is cursor-based, not page-based.** Unlike other endpoints in this API this one does not accept a `page` parameter. Make the first request without `cursor`, then pass the `next_cursor` from each response as the `cursor` of the next request. When `next_cursor` is `null` you have reached the end of the result set; a page may hold fewer than `page_size` entries while more follow, so do not stop early. Treat the cursor as opaque — its format is not part of this contract.\n\nA cursor is a position within one specific result set. It is tied to the `from`, `to`, `metric_id`, `org_unit_id`, `type` and `current` it was issued for, and replaying it against a different combination returns 422 rather than silently skipping rows. To move a window forward, start a new walk without a cursor.\n\nValues are returned grouped by org unit and ordered by timestamp within each org unit, not in global chronological order across sites.\n\n`from` and `to` are required and are interpreted as UTC midnight, so the window is `timestamp >= from` and `timestamp < to`. Note this is a UTC window, not the organisation's business day.\n\nOnly unfiltered values are returned. Where a metric is broken down by a filter, Sona stores the per-filter values alongside the unfiltered total; this endpoint returns the total, so values can be summed without double counting.\n\n`metric_id` is required — call `GET /api/v1/metrics` first to resolve the ids you need, then pass up to 50 of them per request.\n\nA walk is not a consistent snapshot. Values are written while you page, and a value that is re-recorded mid-walk may be returned as a superseded version or not at all on that walk. For incremental sync, re-read the most recent days on each run rather than assuming a completed walk was complete.\n\nReads are rate limited per organisation, and only a couple may run at once — page sequentially rather than in parallel. A 429 carries a `Retry-After` header; wait that long and reissue the same request, cursor included. A 503 means the read could not be served right now and is likewise safe to retry as-is.","operationId":"BackendWeb.ApiV1.MetricValuesController.index","parameters":[{"description":"Start of the window (inclusive), as YYYY-MM-DD, interpreted as UTC midnight","in":"query","name":"from","required":true,"type":"string"},{"description":"End of the window (exclusive), as YYYY-MM-DD, interpreted as UTC midnight. May be at most 31 days after `from`.","in":"query","name":"to","required":true,"type":"string"},{"description":"The metric ids to return values for, from `GET /api/v1/metrics`. Repeat the parameter (`metric_id[]=a&metric_id[]=b`) or pass a comma-separated list. Required, and at most 50 — an unfiltered read of this table cannot be served efficiently.","in":"query","items":{"type":"string"},"name":"metric_id","required":true,"type":"array"},{"description":"Restrict to these org unit (site) ids. Repeat the parameter or pass a comma-separated list. Omit to return all sites. At most 100.","in":"query","items":{"type":"string"},"name":"org_unit_id","required":false,"type":"array"},{"description":"Restrict to these value types. Repeat the parameter or pass a comma-separated list. Omit to return all types.","in":"query","items":{"enum":["actual","system_forecasted","human_forecasted","budget","last_year"],"type":"string"},"name":"type","required":false,"type":"array"},{"description":"Defaults to true: only the latest recorded version of each value is returned. Sona keeps the full history of every value; pass `false` to include superseded versions as well.","in":"query","name":"current","required":false,"type":"boolean"},{"description":"Number of values per page. Defaults to 1000, maximum 10000.","in":"query","name":"page_size","required":false,"type":"integer"},{"description":"The `next_cursor` from the previous response, with every other parameter unchanged. Omit for the first page.","in":"query","name":"cursor","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StoredMetricValuesPage"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden — this API credential is not allowed this endpoint","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too Many Requests — retry after the `Retry-After` header","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Service Unavailable — retry the same request","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Metric Values","tags":["MetricValues"]}},"/api/v1/metrics":{"get":{"description":"List the metric definitions configured for the organisation. Use this to resolve a metric's `id` for the `metric_id` filter on `GET /api/v1/metric_values` and for the `/api/v1/metrics/{metric_id}/store` endpoints, rather than hardcoding metric ids.\n\nMetrics are configured per organisation and `name` is customer-defined, so `name` is not stable across organisations. Where Sona recognises a metric as one of its standard ones, `type` carries that meaning from a fixed vocabulary and is stable; `type` is null for metrics the customer defined themselves, so match on `id` and fall back to `name` for those.","operationId":"BackendWeb.ApiV1.MetricsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Metrics"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Metrics","tags":["Metrics"]}},"/api/v1/metrics/{metric_id}/store":{"post":{"description":"Store the values of a given metric for certain times","operationId":"BackendWeb.ApiV1.MetricsController.store","parameters":[{"description":"Metric id","in":"path","name":"metric_id","required":true,"type":"string"},{"description":"The org unit id","in":"query","name":"org_unit_id","required":true,"type":"string"},{"description":"The type of the metric","in":"query","name":"type","required":true,"type":"string"},{"description":"The metrics to be stored","in":"body","name":"data","required":false,"schema":{"items":{"$ref":"#/definitions/MetricValueParams"},"type":"array"}}],"responses":{"200":{"description":"Success","schema":{"example":{"items":[{"current":true,"current_start_timestamp":"2024-06-15T09:12:44Z","id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","metric_id":"019d3000-1dcf-7be2-8e71-7111130259ae","org_unit_id":"6d8e2f10-3b4a-4c5d-8e9f-0a1b2c3d4e5f","organisation_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","timestamp":"2024-06-15T07:00:00Z","type":"actual","value":12.5}]},"properties":{"items":{"items":{"$ref":"#/definitions/MetricValue"},"type":"array"}},"type":"object"}},"400":{"description":"Bad Request - invalid type or data","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found - metric or org unit not found","schema":{"$ref":"#/definitions/MetricsErrorResponse"}}},"summary":"Store the values of a given metric for certain times","tags":["Metrics"]}},"/api/v1/metrics/{metric_id}/store_bulk":{"post":{"description":"Store metric values for multiple org units. Either all values are stored or none (all-or-nothing).","operationId":"BackendWeb.ApiV1.MetricsController.store_bulk","parameters":[{"description":"Metric id","in":"path","name":"metric_id","required":true,"type":"string"},{"description":"The metrics to be stored for multiple org units grouped by type","in":"body","name":"data","required":false,"schema":{"properties":{"data":{"description":"Map of type to org_unit_id to array of values","example":{"actual":{"org_unit_id_1":[{"timestamp":"2020-01-01T07:00:00Z","value":1.0}]},"system_forecasted":{"org_unit_id_2":[{"timestamp":"2020-01-01T07:00:00Z","value":2.0}]}},"type":"object"}},"type":"object"}}],"responses":{"200":{"description":"Success","schema":{"example":{"total_inserted":48},"properties":{"total_inserted":{"description":"Total number of metric values inserted","type":"integer"}},"type":"object"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/MetricsErrorResponse"}}},"summary":"Store metric values for multiple org units in a single transaction","tags":["Metrics"]}},"/api/v1/metrics/{metric_id}/store_historical":{"post":{"description":"Store metric values for historical backfill. Unlike /store, this endpoint requires a current_timestamp parameter to specify when the data was recorded.","operationId":"BackendWeb.ApiV1.MetricsController.store_historical","parameters":[{"description":"Metric id","in":"path","name":"metric_id","required":true,"type":"string"},{"description":"The org unit id","in":"query","name":"org_unit_id","required":true,"type":"string"},{"description":"The type of the metric","in":"query","name":"type","required":true,"type":"string"},{"description":"The timestamp when this data was recorded (ISO8601 format, must be in the past)","in":"query","name":"current_timestamp","required":true,"type":"string"},{"description":"The metrics to be stored","in":"body","name":"data","required":false,"schema":{"items":{"$ref":"#/definitions/MetricValueParams"},"type":"array"}}],"responses":{"200":{"description":"Success","schema":{"example":{"items":[{"current":true,"current_start_timestamp":"2024-06-15T09:12:44Z","id":"a3c1e7b2-4d5f-4890-b12c-3e4f56789abc","metric_id":"019d3000-1dcf-7be2-8e71-7111130259ae","org_unit_id":"6d8e2f10-3b4a-4c5d-8e9f-0a1b2c3d4e5f","organisation_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","timestamp":"2024-06-15T07:00:00Z","type":"actual","value":12.5}]},"properties":{"items":{"items":{"$ref":"#/definitions/MetricValue"},"type":"array"}},"type":"object"}},"400":{"description":"Bad Request - invalid current_timestamp","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden: the token can't use this endpoint (`ErrorResponse`), or the values could not be stored (`MetricsErrorResponse`)","schema":{"$ref":"#/definitions/MetricsForbiddenResponse"}},"404":{"description":"Not Found - metric or org unit not found","schema":{"$ref":"#/definitions/MetricsErrorResponse"}}},"summary":"Store historical metric values with a custom current_timestamp","tags":["Metrics"]}},"/api/v1/metrics/{metric_id}/store_historical_bulk":{"post":{"description":"Store metric values for multiple org units with a custom current_timestamp for historical backfill. Similar to /store_bulk but requires a current_timestamp parameter. Either all values are stored or none (all-or-nothing transaction).","operationId":"BackendWeb.ApiV1.MetricsController.store_historical_bulk","parameters":[{"description":"Metric id","in":"path","name":"metric_id","required":true,"type":"string"},{"description":"The timestamp when this data was recorded (ISO8601 format, must be in the past)","in":"query","name":"current_timestamp","required":true,"type":"string"},{"description":"The metrics to be stored for multiple org units grouped by type","in":"body","name":"data","required":false,"schema":{"properties":{"data":{"description":"Map of type to org_unit_id to array of values","example":{"actual":{"org_unit_id_1":[{"timestamp":"2020-01-01T07:00:00Z","value":1.0}]},"system_forecasted":{"org_unit_id_2":[{"timestamp":"2020-01-01T07:00:00Z","value":2.0}]}},"type":"object"}},"type":"object"}}],"responses":{"200":{"description":"Success","schema":{"example":{"total_inserted":48},"properties":{"total_inserted":{"description":"Total number of metric values inserted","type":"integer"}},"type":"object"}},"400":{"description":"Bad Request - invalid current_timestamp or data","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found - metric or org unit not found","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/MetricsErrorResponse"}}},"summary":"Store historical metric values for multiple org units with a custom timestamp","tags":["Metrics"]}},"/api/v1/metrics/{metric_id}/{org_unit_id}/latest_timestamp":{"get":{"description":"Returns the latest timestamp recorded for the specified metric and org unit. If nothing has been recorded, or the metric or org unit does not exist, `timestamp` is null.","operationId":"BackendWeb.ApiV1.MetricsController.latest_timestamp","parameters":[{"description":"Metric id","in":"path","name":"metric_id","required":true,"type":"string"},{"description":"The org unit id","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The type of the metric","in":"query","name":"type","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"example":{"timestamp":"2024-06-15T07:00:00Z"},"properties":{"timestamp":{"description":"The latest recorded timestamp for the metric (ISO 8601, UTC); null if none","format":"date-time","type":"string","x-nullable":true}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/MetricsErrorResponse"}}},"summary":"Get the latest timestamp of a given metric","tags":["Metrics"]}},"/api/v1/org_attributes":{"get":{"description":"Returns all attribute definitions for the authenticated organisation.","operationId":"BackendWeb.ApiV1.OrgAttributesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgAttributes"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid pagination or filter parameters. `message` is an object of field names to lists of messages.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}}},"summary":"List all org attributes","tags":["OrgAttributes"]},"post":{"description":"Creates a new attribute definition for the organisation.","operationId":"BackendWeb.ApiV1.OrgAttributesController.create","parameters":[{"description":"Attribute parameters","in":"body","name":"org_attribute","required":true,"schema":{"$ref":"#/definitions/OrgAttributeCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/OrgAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}}},"summary":"Create an org attribute","tags":["OrgAttributes"]}},"/api/v1/org_attributes/batch_query":{"post":{"description":"Returns the resolved value of each requested attribute for each org unit on each date in the given range. Values are resolved using the most specific org-unit-level override, falling back to the organisation-level default. A value of null means the attribute is not configured, while the string \"excluded\" means the org unit is explicitly excluded from that attribute. If an org unit has no data for any requested attribute, its entry is null.","operationId":"BackendWeb.ApiV1.OrgAttributesBatchController.batch_query","parameters":[{"description":"Batch query parameters","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/BatchQueryParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/BatchQueryResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unknown org units or attributes (`message` is a string), or invalid parameters (`message` is an object of field names to lists of messages).","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Batch query org attribute values","tags":["OrgAttributesBatch"]}},"/api/v1/org_attributes/{id}":{"delete":{"description":"Deletes an attribute definition. Fails if the attribute is used by productivity rulesets. Cascades deletion to all org unit attribute overrides.","operationId":"BackendWeb.ApiV1.OrgAttributesController.delete","parameters":[{"description":"Org attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity - attribute in use","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete an org attribute","tags":["OrgAttributes"]},"get":{"description":"Returns a single attribute definition by ID.","operationId":"BackendWeb.ApiV1.OrgAttributesController.show","parameters":[{"description":"Org attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an org attribute","tags":["OrgAttributes"]},"put":{"description":"Updates an existing attribute definition. Slug cannot be changed.","operationId":"BackendWeb.ApiV1.OrgAttributesController.update","parameters":[{"description":"Org attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"},{"description":"Attribute parameters","in":"body","name":"org_attribute","required":true,"schema":{"$ref":"#/definitions/OrgAttributeUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}}},"summary":"Update an org attribute","tags":["OrgAttributes"]}},"/api/v1/org_unit_attributes":{"get":{"description":"Returns all attribute overrides for a given org unit.","operationId":"BackendWeb.ApiV1.OrgUnitAttributesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Org unit ID (required)","format":"uuid","in":"query","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitAttributes"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"`org_unit_id` is missing or not a valid UUID (string `message`), or the filter or pagination parameters are invalid (`message` is an object of parameter names to error lists).","schema":{"$ref":"#/definitions/DetailedErrorResponse"}}},"summary":"List org unit attributes","tags":["OrgUnitAttributes"]},"post":{"description":"Creates a new attribute override for an org unit. Optionally schedule the value with start_date and end_date (both inclusive).","operationId":"BackendWeb.ApiV1.OrgUnitAttributesController.create","parameters":[{"description":"Org unit attribute parameters","in":"body","name":"org_unit_attribute","required":true,"schema":{"$ref":"#/definitions/OrgUnitAttributeCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/OrgUnitAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Field validation failed (`{\"errors\": {...}}`), or a referenced ID is missing, not a valid UUID or not in this organisation, or the value is used by a ruleset (`{\"error\", \"message\"}` with a string `message`).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Create an org unit attribute","tags":["OrgUnitAttributes"]}},"/api/v1/org_unit_attributes/{id}":{"delete":{"description":"Deletes an org unit attribute override. The org unit will fall back to the organisation-level default value. Fails with 422 when the value has no org-level default and is required by productivity rulesets assigned to the org unit (the delete would leave those rules unable to compute).","operationId":"BackendWeb.ApiV1.OrgUnitAttributesController.delete","parameters":[{"description":"Org unit attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity - attribute value in use","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete an org unit attribute","tags":["OrgUnitAttributes"]},"get":{"description":"Returns a single org unit attribute override by ID.","operationId":"BackendWeb.ApiV1.OrgUnitAttributesController.show","parameters":[{"description":"Org unit attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an org unit attribute","tags":["OrgUnitAttributes"]},"put":{"description":"Updates an existing org unit attribute override. Fails with 422 when the change would leave a required attribute unresolved — excluding the value (which masks the org-level default) or shrinking its active span — for productivity rulesets assigned to the org unit.","operationId":"BackendWeb.ApiV1.OrgUnitAttributesController.update","parameters":[{"description":"Org unit attribute ID","format":"uuid","in":"path","name":"id","required":true,"type":"string"},{"description":"Org unit attribute parameters","in":"body","name":"org_unit_attribute","required":true,"schema":{"$ref":"#/definitions/OrgUnitAttributeUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitAttribute"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Field validation failed (`{\"errors\": {...}}`), or the value is used by a ruleset (`{\"error\", \"message\"}` with a string `message`).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Update an org unit attribute","tags":["OrgUnitAttributes"]}},"/api/v1/org_unit_types":{"get":{"description":"Get a list of Org Unit Types","operationId":"BackendWeb.ApiV1.OrgUnitTypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/OrgUnitTypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Org Unit Types","tags":["OrgUnitTypes"]},"post":{"description":"Create an Org Unit Type","operationId":"BackendWeb.ApiV1.OrgUnitTypesController.create","parameters":[{"description":"The org unit type parameters","in":"body","name":"org_unit_type","required":false,"schema":{"$ref":"#/definitions/OrgUnitTypeCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create an Org Unit Type","tags":["OrgUnitTypes"]}},"/api/v1/org_unit_types/{org_unit_type_id}":{"get":{"description":"Get an individual org unit type. See the listing endpoint for a description of org unit types","operationId":"BackendWeb.ApiV1.OrgUnitTypesController.show","parameters":[{"description":"Sona unique identifier of the org_unit_type","in":"path","name":"org_unit_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual org unit type","tags":["OrgUnitTypes"]}},"/api/v1/org_units":{"get":{"description":"An org unit in Sona represents a single organisation unit of a business. So for example if a business is structured as Head Office -> Regions -> Locations -> Departments then an org unit could be any one of those things. Which one it is can be identified from the `org_unit_type` field. Every org unit but one for an organisation will have a `parent_org_unit` which can be used to reconstruct the hierarchy.\n\n**Important**: By default, only base entities (typically the lowest-level units such as Sites or Departments) are returned. To retrieve the full hierarchy including parent org units (e.g. Regions, Areas), you must pass `base_only=false`. Example: `GET /api/v1/org_units?base_only=false`\n\n**Pagination**: Results are paginated. Use the `page` and `page_size` query parameters to navigate. The default page size is 10. Note: the parameter name is `page` (not `page_number`). The response includes `page_number`, `page_size`, `total_pages`, and `total_entries` metadata to assist with iteration.","operationId":"BackendWeb.ApiV1.OrgUnitsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"collectionFormat":"csv","description":"The external ids to filter by. Only org units with a matching external connection are returned.","in":"query","items":{"type":"string"},"name":"external_ids","required":false,"type":"array"},{"description":"Whether only base entities should be returned. **Defaults to `true`**, which means only the lowest-level org units (e.g. Sites, Departments) are returned. Set to `false` to include all org units in the hierarchy, including parent units such as Regions and Areas. This is required if you need to resolve `parent_org_unit` references.","in":"query","name":"base_only","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/OrgUnits"},"page_number":{"description":"The current page number (1-indexed)","type":"integer"},"page_size":{"description":"The number of items per page (default: 10)","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Org Units","tags":["OrgUnits"]},"post":{"description":"Create an Org Unit","operationId":"BackendWeb.ApiV1.OrgUnitsController.create","parameters":[{"description":"The org unit parameters","in":"body","name":"org_unit","required":false,"schema":{"$ref":"#/definitions/OrgUnitCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnit"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create an Org Unit","tags":["OrgUnits"]}},"/api/v1/org_units/{org_unit_id}":{"get":{"description":"Get an individual org unit. See the org unit listing endpoint for a description of org units","operationId":"BackendWeb.ApiV1.OrgUnitsController.show","parameters":[{"description":"Sona unique identifier of the org_unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnit"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual org unit","tags":["OrgUnits"]},"put":{"description":"Update an Org Unit","operationId":"BackendWeb.ApiV1.OrgUnitsController.update","parameters":[{"description":"The Sona unique identifier of org unit to update","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The org unit parameters","in":"body","name":"org_unit","required":false,"schema":{"$ref":"#/definitions/OrgUnitUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnit"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an Org Unit","tags":["OrgUnits"]}},"/api/v1/org_units/{org_unit_id}/agencies":{"get":{"description":"Returns a paginated list of agencies linked to the organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.AgenciesController.index","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/OrgUnitAgencies"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets agencies linked to the organisational unit","tags":["Agencies"]}},"/api/v1/org_units/{org_unit_id}/agencies/{agency_id}/link":{"delete":{"description":"Unlinks an agency to the organisational unit","operationId":"BackendWeb.ApiV1.OrgUnits.AgenciesController.unlink","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Sona unique identifier of the agency","in":"path","name":"agency_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Unlinks an agency to the organisational unit","tags":["Agencies"]},"post":{"description":"Links an agency to the organisational unit","operationId":"BackendWeb.ApiV1.OrgUnits.AgenciesController.link","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Sona unique identifier of the agency","in":"path","name":"agency_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Links an agency to the organisational unit","tags":["Agencies"]}},"/api/v1/org_units/{org_unit_id}/features":{"delete":{"description":"Deletes the features for the organisational unit. Succeeds with 204 even if the organisational unit has no features or does not exist, so it is safe to repeat.","operationId":"BackendWeb.ApiV1.OrgUnits.FeatureSetController.delete","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The org unit ID is not a valid UUID (`ErrorResponse`), or the features could not be deleted (`ValidationErrors`)","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Deletes the organisational unit features","tags":["FeatureSet"]},"get":{"description":"Returns the features of the organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.FeatureSetController.show","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitFeatureSet"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets the organisational unit features","tags":["FeatureSet"]},"post":{"description":"Creates the features for the organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.FeatureSetController.create","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The organisational unit features parameters","in":"body","name":"feature_set","required":true,"schema":{"$ref":"#/definitions/OrgUnitFeatureSetParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitFeatureSet"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The feature set failed validation (`{\"errors\": {...}}`), or the org unit ID is not a valid UUID (`{\"error\", \"message\"}` with a string `message`).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Creates new organisational unit features","tags":["FeatureSet"]},"put":{"description":"Updates the features for the organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.FeatureSetController.update","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The organisational unit features parameters","in":"body","name":"feature_set","required":true,"schema":{"$ref":"#/definitions/OrgUnitFeatureSetParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitFeatureSet"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The feature set failed validation (`{\"errors\": {...}}`), or the org unit ID is not a valid UUID (`{\"error\", \"message\"}` with a string `message`).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Updates the organisational unit features","tags":["FeatureSet"]}},"/api/v1/org_units/{org_unit_id}/location":{"delete":{"description":"Delete the org unit's location","operationId":"BackendWeb.ApiV1.OrgUnitLocationController.delete","parameters":[{"description":"Sona unique identifier of the org unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitLocation"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: the `org_unit_id` is not a valid UUID (`{\"errors\": {\"org_units_id\": [\"is invalid\"]}}`).","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete the org unit's location","tags":["OrgUnitLocation"]},"get":{"description":"Returns the location of an org unit","operationId":"BackendWeb.ApiV1.OrgUnitLocationController.show","parameters":[{"description":"Sona unique identifier of the org unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitLocation"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: the `org_unit_id` is not a valid UUID (`{\"errors\": {\"org_units_id\": [\"is invalid\"]}}`).","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an org unit's location","tags":["OrgUnitLocation"]},"post":{"description":"Create or update an org unit's location.","operationId":"BackendWeb.ApiV1.OrgUnitLocationController.create","parameters":[{"description":"Sona unique identifier of the org unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The org unit parameters","in":"body","name":"org_unit","required":false,"schema":{"$ref":"#/definitions/OrgUnitLocationUpsertParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnitLocation"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Service Unavailable — the postcode lookup timed out and nothing was saved; retry the same request","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create or update an org unit's location","tags":["OrgUnitLocation"]}},"/api/v1/org_units/{org_unit_id}/productivity/calculate":{"post":{"description":"Runs the productivity engine calculator with the supplied shifts, rules, metrics, and roles.\nReturns staffing demand per role per time interval.\n\n**Metrics behaviour:**\n- If `metrics` is omitted, metric data is auto-loaded from the database using `metric_id` values\n  referenced in demand rules. The fallback order for metric values is: actual, then human forecasted,\n  then system forecasted.\n- If `metrics` is provided, the supplied data is used directly (useful for what-if scenarios).\n\n**Rounding behaviour:**\n- If `rounding` is omitted, raw float values are returned.\n- Supported modes: `none`, `ceil`, `floor`, `threshold`.\n- `threshold` mode requires a `threshold` value between 0 and 1.\n\n**Date range limit:** `to_utc - from_utc` must not exceed 7 days.\n","operationId":"BackendWeb.ApiV1.OrgUnits.ProductivityCalculateController.calculate","parameters":[{"description":"UUID of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Calculation parameters","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/ProductivityCalculateRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ProductivityCalculateResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Org unit not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Calculate productivity demand for an organisational unit","tags":["ProductivityCalculate"]}},"/api/v1/org_units/{org_unit_id}/productivity/interpolation_preview":{"post":{"description":"Resolves attribute expressions in productivity rules for each date in a range.\nReturns concrete rule parameters grouped by date, showing how scheduled attribute\nchanges affect rule interpolation over time.\n\n**Rules source:**\n- If `rules` is provided, those rules are interpolated.\n- If `rules` is omitted, rules are loaded from the org unit's productivity config.\n\n**Date range limit:** `to_date - from_date` must not exceed 90 days.\n","operationId":"BackendWeb.ApiV1.OrgUnits.InterpolationPreviewController.preview","parameters":[{"description":"UUID of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Preview parameters","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/InterpolationPreviewRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/InterpolationPreviewResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Org unit not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Preview rule interpolation across a date range","tags":["InterpolationPreview"]}},"/api/v1/org_units/{org_unit_id}/productivity_config":{"get":{"description":"Returns the productivity configuration for the organisational unit, including rule sets and rules. Returns null if no config exists.","operationId":"BackendWeb.ApiV1.OrgUnits.ProductivityConfigController.show","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ProductivityConfigResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets the organisational unit productivity config","tags":["ProductivityConfig"]}},"/api/v1/org_units/{org_unit_id}/public_holidays":{"delete":{"description":"Remove selected public holidays from the org unit public holidays list","operationId":"BackendWeb.ApiV1.OrgUnitsController.delete_public_holidays","parameters":[{"description":"Sona unique identifier of the org unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Public holidays IDs to remove","in":"body","name":"public_holidays","required":false,"schema":{"$ref":"#/definitions/OrgUnitPublicHolidaysParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnit"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Remove selected public holidays from the org unit public holidays list","tags":["OrgUnits"]},"post":{"description":"Set public holidays for the org unit","operationId":"BackendWeb.ApiV1.OrgUnitsController.set_public_holidays","parameters":[{"description":"Sona unique identifier of the org unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"Public holidays parameters","in":"body","name":"public_holidays","required":false,"schema":{"$ref":"#/definitions/OrgUnitPublicHolidaysParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrgUnit"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"No org unit ID was provided","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Set public holidays for the org unit","tags":["OrgUnits"]}},"/api/v1/org_units/{org_unit_id}/time_and_attendance_policy":{"delete":{"description":"Deletes the time and attendance policy for an organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.TimeAndAttendancePolicyController.delete","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: the `org_unit_id` is not a valid UUID (\"Invalid org_unit_id\").","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Deletes an organisational unit's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"get":{"description":"Returns the time and attendance policy of a organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.TimeAndAttendancePolicyController.show","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets a organisational unit's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"post":{"description":"Creates the time and attendance policy for an organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.TimeAndAttendancePolicyController.create","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The organisational unit's T&A policy parameters","in":"body","name":"time_and_attendance_policy","required":true,"schema":{"$ref":"#/definitions/TimeAndAttendancePolicyParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a policy field fails validation; `{\"error\", \"message\"}` for an invalid `org_unit_id` (\"Invalid org_unit_id\"), a policy that is not an object (\"Invalid time_and_attendance_policy\") or missing parameters.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Creates an organisational unit's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"put":{"description":"Updates the time and attendance policy for an organisational unit.","operationId":"BackendWeb.ApiV1.OrgUnits.TimeAndAttendancePolicyController.update","parameters":[{"description":"Sona unique identifier of the organisational unit","in":"path","name":"org_unit_id","required":true,"type":"string"},{"description":"The organisational unit's T&A policy parameters","in":"body","name":"time_and_attendance_policy","required":true,"schema":{"$ref":"#/definitions/TimeAndAttendancePolicyParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a policy field fails validation; `{\"error\", \"message\"}` for an invalid `org_unit_id` (\"Invalid org_unit_id\"), a policy that is not an object (\"Invalid time_and_attendance_policy\") or missing parameters.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Updates an organisational unit's time and attendance policy","tags":["TimeAndAttendancePolicy"]}},"/api/v1/organisation/calendar":{"get":{"description":"Returns the calendar settings of the API key's organisation: the day its week starts on, the time its business day starts, and the start of its fiscal year. Use them to bucket days, weeks and fiscal years the way the organisation does.","operationId":"BackendWeb.ApiV1.OrganisationCalendarController.show","parameters":[],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OrganisationCalendar"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets the organisation calendar","tags":["OrganisationCalendar"]}},"/api/v1/orgs/{organisation_id}/metric-calculations":{"get":{"description":"Returns every metric calculation belonging to the organisation, deduplicated by formula content. Each entry includes an `applied_at` array listing the org_units (one per restaurant / location) that use that exact calculation. When the same name maps to different formulas across configs — or differs in `binding_type` or `filter` of any variable — each variant appears as a separate entry with disjoint `applied_at` lists. Per-config display flags (`show_on_roster`, `key_metric`, etc.) are intentionally omitted. Each entry carries a normalised `ast` so consumers can evaluate the formula without parsing the Sona DSL themselves; calculations whose formulas use syntax outside the supported grammar are omitted from the response (with a server-side log) rather than served partial — see the `ast` schema for the supported grammar.","operationId":"BackendWeb.ApiV1.Reporting.MetricCalculationsController.index","parameters":[{"description":"Sona unique identifier of the organisation. Must match the bearer token's organisation.","format":"uuid","in":"path","name":"organisation_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/MetricCalculationsResponse"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Forbidden","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Lists per-customer metric calculations","tags":["MetricCalculations"]}},"/api/v1/pay_groups":{"get":{"description":"List pay groups for the current organisation","operationId":"BackendWeb.ApiV1.PayGroupsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayGroups"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of pay groups","tags":["PayGroups"]}},"/api/v1/pay_groups/{id}":{"get":{"description":"Get a single pay group data.","operationId":"BackendWeb.ApiV1.PayGroupsController.show","parameters":[{"description":"The Sona unique identifier of the pay group.","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PayGroup"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID. `message` maps the parameter to its error, e.g. `{\"id\": \"invalid format\"}`","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a single pay group","tags":["PayGroups"]}},"/api/v1/pay_groups/{pay_group_id}/pay_periods":{"get":{"description":"Returns the pay schedule (pay periods) for the given pay group, ordered by start date descending.","operationId":"BackendWeb.ApiV1.PayGroupPayPeriodsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The Sona unique identifier of the pay group.","in":"path","name":"pay_group_id","required":true,"type":"string"},{"description":"Filter pay periods with start_date on or after this date (ISO 8601, e.g. 2026-01-01)","in":"query","name":"from_date","required":false,"type":"string"},{"description":"Filter pay periods with start_date on or before this date (ISO 8601, e.g. 2026-12-31)","in":"query","name":"to_date","required":false,"type":"string"},{"description":"Filter by pay period state: \"open\" or \"closed\"","enum":["open","closed"],"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayPeriods"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The pay group ID or a filter (`from_date`, `to_date`, `state`) is invalid. `message` is an object mapping each invalid parameter to a single error message (a string), e.g. `{\"state\": \"invalid value, allowed values are: open, closed\"}`.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List pay periods for a pay group","tags":["PayGroupPayPeriods"]}},"/api/v1/pay_lines":{"get":{"description":"List all pay lines for the current organisation.","operationId":"BackendWeb.ApiV1.PayrollRunRowsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"order results by date (asc or desc). By default is asc.","enum":["asc","desc"],"in":"query","name":"order_by_date","required":false,"type":"string"},{"description":"The start date time in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"start_datetime","required":false,"type":"string"},{"description":"The end date time in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"end_datetime","required":false,"type":"string"},{"description":"Type of pay line","enum":["regular","amendment"],"in":"query","name":"entry_type","required":false,"type":"string"},{"description":"Filter by user id","in":"query","name":"user_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayLines"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of pay lines","tags":["PayLines"]}},"/api/v1/pay_variants":{"get":{"description":"List all pay variants for the organisation","operationId":"BackendWeb.ApiV1.PayVariantsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayVariants"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: pagination or sorting parameters are invalid. `message` maps parameter names to errors.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of pay variants","tags":["PayVariants"]}},"/api/v1/payroll_payment_deduction_types":{"get":{"description":"Returns a paginated list of payroll payment and deduction types for the current organisation.","operationId":"BackendWeb.ApiV1.PayrollPaymentDeductionTypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayrollPaymentDeductionTypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List payroll payment/deduction types","tags":["PayrollPaymentDeductionTypes"]},"post":{"description":"Create a new payroll payment or deduction type for the current organisation.\n\nThe `type` field distinguishes payments from deductions: \"Payment\", \"Deduction\", or \"Cost\".\n\nThe `external_type` field must match the company pay elements in Shape when Sona Pay sync\nis enabled for the organisation; it becomes a required field in that case.\n\nThe `system_pay_code` field is optional and restricted to: final_holiday_pay, holiday_pay, ssp.\nOnly one final_holiday_pay type and one ssp type are allowed per organisation.\n\nWARNING: Changing a pay_code after TGP rules reference it may cause errors in running\nor future TGP jobs.\n","operationId":"BackendWeb.ApiV1.PayrollPaymentDeductionTypesController.create","parameters":[{"description":"The payroll payment/deduction type parameters","in":"body","name":"payroll_payment_deduction_type","required":false,"schema":{"$ref":"#/definitions/PayrollPaymentDeductionTypeCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/PayrollPaymentDeductionType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Validation failed. `message` maps each invalid field to its error messages, e.g. `{\"pay_code\": [\"can't be blank\"]}`","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a payroll payment/deduction type","tags":["PayrollPaymentDeductionTypes"]}},"/api/v1/payroll_payment_deduction_types/{id}":{"get":{"description":"Get a single payroll payment or deduction type by its Sona unique identifier.","operationId":"BackendWeb.ApiV1.PayrollPaymentDeductionTypesController.show","parameters":[{"description":"The Sona unique identifier of the payroll payment/deduction type.","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PayrollPaymentDeductionType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a single payroll payment/deduction type","tags":["PayrollPaymentDeductionTypes"]},"put":{"description":"Update an existing payroll payment or deduction type.\n\nThe `additional_properties` field uses merge semantics: only the keys you provide will be\nupdated; existing keys not included in the request are preserved. Omitting\n`additional_properties` entirely leaves the current value unchanged.\n\nWARNING: Changing a pay_code after TGP rules reference it may cause errors in running\nor future TGP jobs. Changing system_pay_code may affect holiday pay or SSP calculations.\n","operationId":"BackendWeb.ApiV1.PayrollPaymentDeductionTypesController.update","parameters":[{"description":"The Sona unique identifier of the payroll payment/deduction type.","in":"path","name":"id","required":true,"type":"string"},{"description":"The payroll payment/deduction type parameters","in":"body","name":"payroll_payment_deduction_type","required":false,"schema":{"$ref":"#/definitions/PayrollPaymentDeductionTypeUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PayrollPaymentDeductionType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID (`message` is the string `\"Invalid id format\"`), or validation failed (`message` maps each invalid field to its error messages)","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a payroll payment/deduction type","tags":["PayrollPaymentDeductionTypes"]}},"/api/v1/payroll_submission_statuses":{"get":{"description":"Returns worked shifts with their payroll submission status and, for shifts still pending submission, whether they need attention (\"unreconciled\") and why. This is the programmatic equivalent of the *Review Payroll Records* / *Pending Submission* view.\n\n`submission_status` is `pending` (not yet approved for payroll) or `submitted` (approved for payroll). `reconciliation_status` is `needs_attention` when the shift has an unresolved payroll issue, otherwise `resolved`. The `exceptions` array lists the reasons a shift needs attention: `pay_query` (an exception flagged on the payroll record), `missing_pay_variant`, `shift_in_progress` (payroll record has no end time), or `missing_pay_record` (no payroll record while time & attendance is required).\n\n`min_start_datetime` and `max_start_datetime` are required and may span at most 31 days.","operationId":"BackendWeb.ApiV1.PayrollSubmissionStatusesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Only include shifts starting on or after this time in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"min_start_datetime","required":true,"type":"string"},{"description":"Only include shifts starting on or before this time in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"max_start_datetime","required":true,"type":"string"},{"description":"Filter by the Sona user id of the employee","in":"query","name":"user_id","required":false,"type":"string"},{"description":"Filter by one or more org unit (location) ids, comma-separated","in":"query","name":"org_unit_ids","required":false,"type":"string"},{"description":"Filter by submission status","enum":["pending","submitted"],"in":"query","name":"submission_status","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PayrollSubmissionStatuses"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get payroll submission statuses for worked shifts","tags":["PayrollSubmissionStatuses"]}},"/api/v1/people_we_support":{"get":{"description":"Get a list of people we support","operationId":"BackendWeb.ApiV1.AssignmentsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Only people in this org unit (Sona id)","in":"query","name":"org_unit_id","required":false,"type":"string"},{"description":"Exact match on the external id (`upload_id`)","in":"query","name":"upload_id","required":false,"type":"string"},{"description":"Exact match on `integration_idx`","in":"query","name":"integration_idx","required":false,"type":"string"},{"description":"Case-insensitive 'contains' match on name","in":"query","name":"name","required":false,"type":"string"},{"description":"Current status: `active` or `inactive`. `active` includes people with no status record (`status: null`).","in":"query","name":"status","required":false,"type":"string"},{"description":"Only people updated on or after this UTC datetime (ISO 8601, trailing Z). Only the person's own record is considered, not status or care package changes.","in":"query","name":"updated_at","required":false,"type":"string"},{"description":"`asc` or `desc`, by name. When omitted the order is unspecified, as before.","in":"query","name":"order","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PeopleWeSupportDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of people we support","tags":["PeopleWeSupport"]},"post":{"description":"Create person we support","operationId":"BackendWeb.ApiV1.AssignmentsController.create","parameters":[{"description":"Person we support parameters","in":"body","name":"people_we_support","required":false,"schema":{"$ref":"#/definitions/PeopleWeSupportCreateParams"}}],"responses":{"201":{"description":"Success","schema":{"$ref":"#/definitions/PeopleWeSupportData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The org unit was not found in the organisation. A missing `org_unit_id` is also returned as 404 (`org_unit can't be blank`; known issue: it is a validation error and should be 422).","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create person we support","tags":["PeopleWeSupport"]}},"/api/v1/people_we_support/{person_we_support_id}":{"get":{"description":"Get one person we support by Sona id, or by `upload_id` when `is_idx=true`. 404 if the person is not in your organisation.","operationId":"BackendWeb.ApiV1.AssignmentsController.show","parameters":[{"description":"Sona id, or `upload_id` when `is_idx=true`","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Set to true to look the person up by `upload_id`","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PeopleWeSupportData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a person we support","tags":["PeopleWeSupport"]},"put":{"description":"Update person we support, by Sona id or by `upload_id` when `is_idx=true`","operationId":"BackendWeb.ApiV1.AssignmentsController.update","parameters":[{"description":"Sona id, or `upload_id` when `is_idx=true`","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Set to true to look the person up by `upload_id`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"People we support update parameters","in":"body","name":"people_we_support","required":false,"schema":{"$ref":"#/definitions/PeopleWeSupportUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PeopleWeSupportData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update person we support","tags":["PeopleWeSupport"]}},"/api/v1/people_we_support/{person_we_support_id}/budgets":{"get":{"description":"Get list of people we support budget entries","operationId":"BackendWeb.ApiV1.AssignmentBudgetsController.index","parameters":[{"description":"Sona unique identifier of the person we support","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PeopleWeSupportBudgetsList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get list of people we support budget entries","tags":["PeopleWeSupportBudgets"]},"post":{"description":"Create people we support budget entry","operationId":"BackendWeb.ApiV1.AssignmentBudgetsController.create","parameters":[{"description":"Sona unique identifier of the person we support","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"People we support budget parameters","in":"body","name":"people_we_support_budget","required":false,"schema":{"$ref":"#/definitions/PeopleWeSupportCreateBudgetParams"}}],"responses":{"201":{"description":"Success","schema":{"$ref":"#/definitions/PeopleWeSupportBudgets"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a budget field fails validation; `{\"error\", \"message\"}` for an invalid request.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create people we support budget entry","tags":["PeopleWeSupportBudgets"]}},"/api/v1/people_we_support/{person_we_support_id}/budgets/{budget_id}":{"put":{"description":"Update people we support budget entry","operationId":"BackendWeb.ApiV1.AssignmentBudgetsController.update","parameters":[{"description":"Sona unique identifier of the person we support","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Sona unique identifier of the budget entry","in":"path","name":"budget_id","required":true,"type":"string"},{"description":"People we support budget parameters","in":"body","name":"people_we_support_budget","required":false,"schema":{"$ref":"#/definitions/PeopleWeSupportUpdateBudgetParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PeopleWeSupportBudgets"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a budget field fails validation; `{\"error\", \"message\"}` for an invalid request.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update people we support budget entry","tags":["PeopleWeSupportBudgets"]}},"/api/v1/people_we_support/{person_we_support_id}/care_packages":{"get":{"description":"Newest first. Requires demand-based rostering to be enabled for your organisation (403 otherwise). `updated_at` counts a change to the package (including being superseded) or a new approval status on any of its hour lines.\n","operationId":"BackendWeb.ApiV1.CarePackagesController.index_for_pws","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Sona id, or `upload_id` when `is_idx=true`","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Set to true to look the person up by `upload_id`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"Exact match on the package code","in":"query","name":"idx","required":false,"type":"string"},{"description":"`true` only packages in force today (started, and not ended; UTC date), `false` only the others: ended, or starting later","in":"query","name":"current","required":false,"type":"boolean"},{"description":"Packages starting on or after this date (YYYY-MM-DD)","in":"query","name":"start_date_from","required":false,"type":"string"},{"description":"Packages starting on or before this date (YYYY-MM-DD)","in":"query","name":"start_date_to","required":false,"type":"string"},{"description":"Only packages changed on or after this UTC datetime","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/CarePackageDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List a person's care packages","tags":["CarePackages"]},"post":{"description":"Creates a care package for a person. **Care packages are never edited.** A change is a new package with a later `start_date`, which ends the person's current package the day before and links it. `start_date` must be after the current package's start (no back-dating), and `idx`, your own id for the package, must be unique in your organisation. Everything is referenced by id, never by name: each hour line names its care type (`type_of_work_id` or `type_of_work_idx`), its service (`org_unit_id` or `org_unit_idx`) and at least one `demand_period_ids` entry, and every demand period must belong to that line's service. All or nothing: one invalid line means no package. Every hour line starts as `pending`. Up to 100 hour lines.","operationId":"BackendWeb.ApiV1.CarePackagesController.create","parameters":[{"description":"Sona id, or `upload_id` when `is_idx=true`","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Set to true to look the person up by `upload_id`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The package","in":"body","name":"care_package","required":true,"schema":{"$ref":"#/definitions/CarePackageCreateParams"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/CarePackageData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Create a care package","tags":["CarePackages"]}},"/api/v1/people_we_support/{person_we_support_id}/status_records":{"get":{"description":"When the person was active or inactive, and why, newest first. Read only. The current status is also on the person (`status`). Requires demand-based rostering to be enabled for your organisation (403 otherwise).","operationId":"BackendWeb.ApiV1.StatusRecordsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Sona id, or `upload_id` when `is_idx=true`","in":"path","name":"person_we_support_id","required":true,"type":"string"},{"description":"Set to true to look the person up by `upload_id`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"`active` or `inactive`","in":"query","name":"status","required":false,"type":"string"},{"description":"Records that started on or after this date (YYYY-MM-DD, UTC)","in":"query","name":"from","required":false,"type":"string"},{"description":"Records that started on or before this date (YYYY-MM-DD, UTC)","in":"query","name":"to","required":false,"type":"string"},{"description":"Records created or changed on or after this time, YYYY-MM-DDThh:mm:ssZ in UTC (e.g. 2026-01-15T10:00:00Z)","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/StatusRecordDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List a person's status history","tags":["PeopleWeSupport"]}},"/api/v1/permission_roles":{"get":{"description":"Permission Roles","operationId":"BackendWeb.ApiV1.PermissionRolesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PermissionRoles"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of permission roles","tags":["PermissionRoles"]}},"/api/v1/permission_roles/employment_periods/{employment_period_id_or_idx}":{"put":{"description":"Update permission roles for an employment period based on employment_period_id OR the external idx","operationId":"BackendWeb.ApiV1.PermissionRolesController.update","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"},{"description":"The permission roles to update for the employment period","in":"body","name":"permission_roles","required":false,"schema":{"$ref":"#/definitions/PermissionRolesLocations"}}],"responses":{"200":{"description":"Success, employment_period permission roles updated","schema":{"$ref":"#/definitions/EmploymentPeriodPermissionRolesUpdateResponse"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The employment period, permission role or org unit was not found. A failed validation is also returned as 404, with a `ValidationErrors` body","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update permission roles for an employment period","tags":["PermissionRoles"]}},"/api/v1/permission_roles/{permission_role_id}/employment_periods/{employment_period_id_or_idx}":{"delete":{"description":"Remove permission roles for an employment period based on employment_period_id OR the external idx","operationId":"BackendWeb.ApiV1.PermissionRolesController.remove_permission_role","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"},{"description":"Sona unique identifier of the permission role to remove","in":"path","name":"permission_role_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success, employment_period permission roles removed","schema":{"$ref":"#/definitions/EmploymentPeriodPermissionRolesUpdateResponse"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The employment period, permission role or org unit was not found. A failed validation is also returned as 404, with a `ValidationErrors` body","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Remove permission roles for an employment period","tags":["PermissionRoles"]},"put":{"description":"Add permission roles for an employment period based on employment_period_id OR the external idx","operationId":"BackendWeb.ApiV1.PermissionRolesController.add_permission_role","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"},{"description":"Sona unique identifier of the permission role to add","in":"path","name":"permission_role_id","required":true,"type":"string"},{"description":"The org unit IDs to associate with the permission role","in":"body","name":"org_unit_ids","required":false,"schema":{"$ref":"#/definitions/AddPermissionRolesPermissionRoleParameters"}}],"responses":{"200":{"description":"Success, employment_period permission roles added","schema":{"$ref":"#/definitions/EmploymentPeriodPermissionRolesUpdateResponse"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The employment period, permission role or org unit was not found. A failed validation is also returned as 404, with a `ValidationErrors` body","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Add permission roles for an employment period","tags":["PermissionRoles"]}},"/api/v1/permission_roles/{permission_role_id}/employment_periods/{employment_period_id_or_idx}/org_units/{org_unit_id}":{"delete":{"description":"Remove org_unit from permission role for an employment period based on employment_period_id OR the external idx","operationId":"BackendWeb.ApiV1.PermissionRolesController.remove_permission_role_org_unit","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"},{"description":"Sona unique identifier of the permission role to update","in":"path","name":"permission_role_id","required":true,"type":"string"},{"description":"Sona unique identifier of the org unit to remove from the permission role","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success, employment_period permission roles updated","schema":{"$ref":"#/definitions/EmploymentPeriodPermissionRolesUpdateResponse"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The employment period, permission role or org unit was not found. A failed validation is also returned as 404, with a `ValidationErrors` body","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Remove org_unit from permission role for an employment period","tags":["PermissionRoles"]},"put":{"description":"Add org_unit to permission role for an employment period based on employment_period_id OR the external idx","operationId":"BackendWeb.ApiV1.PermissionRolesController.add_permission_role_org_unit","parameters":[{"description":"Sona unique identifier of the employment period or external id","in":"path","name":"employment_period_id_or_idx","required":true,"type":"string"},{"description":"Sona unique identifier of the permission role to add org unit to","in":"path","name":"permission_role_id","required":true,"type":"string"},{"description":"Sona unique identifier of the org unit to add to the permission role","in":"path","name":"org_unit_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success, employment_period permission roles updated","schema":{"$ref":"#/definitions/EmploymentPeriodPermissionRolesUpdateResponse"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The employment period, permission role or org unit was not found. A failed validation is also returned as 404, with a `ValidationErrors` body","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Add org_unit to permission role for an employment period","tags":["PermissionRoles"]}},"/api/v1/public_holidays":{"get":{"description":"Get list of public holidays","operationId":"BackendWeb.ApiV1.PublicHolidaysController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/PublicHolidays"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get list of public holidays","tags":["PublicHolidays"]}},"/api/v1/rates_of_pay":{"get":{"description":"A rate of pay defines the monetary compensation for a specific user, type of work, and organisational unit over a date range. Rates can be hourly or salaried and are linked to an employment period. Use the query parameters to filter by user, type of work, org unit, or employment period.","operationId":"BackendWeb.ApiV1.RatesOfPayController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The user_id to filter by","in":"query","name":"user_id","required":false,"type":"string"},{"description":"The type_of_work_id to filter results by","in":"query","name":"type_of_work_id","required":false,"type":"string"},{"description":"The org_unit_id to filter results by","in":"query","name":"org_unit_id","required":false,"type":"string"},{"description":"The employment_period_id to filter results by","in":"query","name":"employment_period_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/RatesOfPay"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of rates of pay","tags":["RatesOfPay"]},"post":{"description":"Create a new rate of pay. See the RatesOfPay listing endpoint for a description of a rate of pay. One of the employment_period_id or employment_period_idx parameters is required. For organisations with UK minimum wage validation enabled, a GBP rate below the apprentice-rate hard floor (£7.55, £8.00 from April 2026) is rejected with a 422 naming the required amount on the rate field. This floor is not age-banded, so a rate that is legal for an apprentice but below the National Living Wage for the employee's age is still accepted. A salaried rate is compared on its hourly equivalent, derived from the contracted hours stored against the employment spec for the rate's start date; where none are stored the comparison is skipped. A currency that is not a recognised three-letter ISO 4217 code is also rejected with a 422.","operationId":"BackendWeb.ApiV1.RatesOfPayController.create","parameters":[{"description":"The rate of pay parameters","in":"body","name":"rate_of_pay","required":false,"schema":{"$ref":"#/definitions/RateOfPayCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/RateOfPay"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid parameters (e.g. bad date or amount, overlapping dates). `message` is a string, or for field validation failures an object of field names to arrays of error messages.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new rate of pay","tags":["RatesOfPay"]}},"/api/v1/rates_of_pay/{rate_of_pay_id_or_idx}":{"delete":{"description":"Delete a rate of pay. This applies the same rules as deleting a rate in the Sona web client: the base (or default) rate of pay cannot be deleted, and a rate that is used by a shift or a payroll time to gross run cannot be deleted. Both are rejected with a 422 explaining why. A token restricted to specific endpoints must allow `DELETE /api/v1/rates_of_pay/:id`, otherwise the response is a 403.","operationId":"BackendWeb.ApiV1.RatesOfPayController.delete","parameters":[{"description":"Sona unique identifier or external id of the rate of pay","in":"path","name":"rate_of_pay_id_or_idx","required":true,"type":"string"},{"description":"IF rate_of_pay_id_or_idx is an external id, this parameter should be set to true","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"204":{"description":"Deleted"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity. For the base or default rate, `message` is a plain string. For a rate used by a shift or payroll run, `message` is an object of field errors, e.g. `{\"id\": [\"Can't be deleted because it is used by one or more shifts\"]}`.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete a rate of pay","tags":["RatesOfPay"]},"get":{"description":"Get an individual rate_of_pay. See the rate_of_pay listing endpoint for a description of rate_of_pay","operationId":"BackendWeb.ApiV1.RatesOfPayController.show","parameters":[{"description":"Sona unique identifier or external id of the rate of pay","in":"path","name":"rate_of_pay_id_or_idx","required":true,"type":"string"},{"description":"If rate_of_pay_id_or_idx parameter is an external id this parameter should be set to true","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/RateOfPay"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual rate_of_pay","tags":["RatesOfPay"]},"put":{"description":"Update an existing rate of pay. See the RatesOfPay listing endpoint for a description of a rate of pay.  One of the id or idx parameters is required. To change the rate of pay's external identifier, send the new value as idx in the request body; rate_of_pay_id_or_idx in the path identifies the rate being updated. If both employment_period_id and employment_period_idx are given employment_period_id has a higher precedence. For organisations with UK minimum wage validation enabled, a GBP rate below the apprentice-rate hard floor (£7.55, £8.00 from April 2026) is rejected with a 422 naming the required amount on the rate field. This floor is not age-banded, so a rate that is legal for an apprentice but below the National Living Wage for the employee's age is still accepted. A salaried rate is compared on its hourly equivalent, derived from the contracted hours stored against the employment spec for the rate's start date; where none are stored the comparison is skipped. A currency that is not a recognised three-letter ISO 4217 code is also rejected with a 422.","operationId":"BackendWeb.ApiV1.RatesOfPayController.update","parameters":[{"description":"Sona unique identifier or external id of the rate of pay","in":"path","name":"rate_of_pay_id_or_idx","required":true,"type":"string"},{"description":"The rate of pay parameters","in":"body","name":"rate_of_pay","required":false,"schema":{"$ref":"#/definitions/RateOfPayUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/RateOfPay"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid parameters (e.g. bad date or amount, overlapping dates). `message` is a string, or for field validation failures an object of field names to arrays of error messages.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an existing rate of pay","tags":["RatesOfPay"]}},"/api/v1/services":{"get":{"description":"Org units with their demand-based rostering setup. Requires demand-based rostering to be enabled for your organisation (403 otherwise). `config` is the config in force today, or null. `updated_at` counts a change to the org unit, any of its configs or any of its demand periods, including a demand period being removed.","operationId":"BackendWeb.ApiV1.ServicesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"`true` only configured services, `false` only unconfigured","in":"query","name":"configured","required":false,"type":"boolean"},{"description":"Case-insensitive 'contains' match on the service name","in":"query","name":"name","required":false,"type":"string"},{"description":"Exact match on the service `idx` (the org unit `upload_id`)","in":"query","name":"idx","required":false,"type":"string"},{"description":"Only services changed on or after this UTC datetime (ISO 8601, trailing Z)","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ServiceDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List services","tags":["Services"]}},"/api/v1/services/{service_id}":{"get":{"description":"One service by Sona id, or by `idx` (its `upload_id`) when `is_idx=true`. 404 if it is not in your organisation.","operationId":"BackendWeb.ApiV1.ServicesController.show","parameters":[{"description":"Sona id, or `idx` when `is_idx=true`","in":"path","name":"service_id","required":true,"type":"string"},{"description":"Set to true to look the service up by `idx`","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ServiceData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a service","tags":["Services"]}},"/api/v1/services/{service_id}/audit_log":{"get":{"description":"Field-level changes to the service's capacity, target ratio and demand periods, newest first (entries made within the same second have no defined order). `actioned_by_source` is `api` for changes made through this API.","operationId":"BackendWeb.ApiV1.ServicesController.audit_log","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Sona id, or `idx` when `is_idx=true`","in":"path","name":"service_id","required":true,"type":"string"},{"description":"Set to true to look the service up by `idx`","in":"query","name":"is_idx","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ServiceAuditLogList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a service's audit log","tags":["Services"]}},"/api/v1/services/{service_id}/config":{"put":{"description":"Creates the config (201) when the service has no open-ended one, otherwise updates it in place (200) using **only the fields you send**, so other fields are never cleared. Send an explicit null to clear an optional field. `start_date` is required when creating. To end demand-based rostering for a service, set `end_date`; there is no delete. This never creates a service. Note: the config in force today is the one returned by GET; this endpoint acts on the open-ended config (no `end_date`), which can be a config that starts in the future. Simultaneous calls for one service are handled one after another, so only the first can create.","operationId":"BackendWeb.ApiV1.ServicesController.upsert_config","parameters":[{"description":"Sona id, or `idx` when `is_idx=true`","in":"path","name":"service_id","required":true,"type":"string"},{"description":"Set to true to look the service up by `idx`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"Config fields","in":"body","name":"config","required":true,"schema":{"$ref":"#/definitions/ServiceConfigParams"}}],"responses":{"200":{"description":"Updated","schema":{"$ref":"#/definitions/ServiceData"}},"201":{"description":"Created","schema":{"$ref":"#/definitions/ServiceData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Create or update a service's config","tags":["Services"]}},"/api/v1/services/{service_id}/demand_periods":{"put":{"description":"**This replaces the whole list.** Any existing demand period you leave out is deleted. To keep or change an existing period, send it with its `id`; an item without an `id` is created as a new period. Periods are never matched by name, and an `id` that is not one of this service's periods is a 422. Array order sets `position`. Overnight periods are fine (`start_time` after `end_time`). Sending the same list twice changes nothing. Demand period names are unique within a service, and periods are saved one at a time in list order, so a request that gives one period a name another period still holds (for example swapping two names) is refused with a 422. Do that in two requests. A period used by a care package hour line cannot be removed (409, with the period ids in `references`).","operationId":"BackendWeb.ApiV1.ServicesController.replace_demand_periods","parameters":[{"description":"Sona id, or `idx` when `is_idx=true`","in":"path","name":"service_id","required":true,"type":"string"},{"description":"Set to true to look the service up by `idx`","in":"query","name":"is_idx","required":false,"type":"boolean"},{"description":"The full list of demand periods","in":"body","name":"periods","required":true,"schema":{"$ref":"#/definitions/DemandPeriodsParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ServiceData"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"A period is still in use","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Replace a service's demand periods","tags":["Services"]}},"/api/v1/shift_types":{"get":{"description":"A shift type is an org unit specific definition of a type of work. It is link to a type of work which defines which users are qualified to claim or be assigned to the shift. Shift types can have default descriptions and start + finish times.","operationId":"BackendWeb.ApiV1.ShiftTypesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The org unit to return shift types for","in":"query","name":"org_unit_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ShiftTypes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Shift Types","tags":["ShiftTypes"]},"post":{"description":"Create a Shift Type","operationId":"BackendWeb.ApiV1.ShiftTypesController.create","parameters":[{"description":"The shift type parameters","in":"body","name":"shift_type","required":false,"schema":{"$ref":"#/definitions/ShiftTypeCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShiftType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a field fails validation; `{\"error\", \"message\"}` with `org_unit not found` or `type_of_work not found` when `org_unit_id` or `type_of_work_id` is missing or not in your organisation.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a Shift Type","tags":["ShiftTypes"]}},"/api/v1/shift_types/{shift_type_id}":{"get":{"description":"Get an individual shift type","operationId":"BackendWeb.ApiV1.ShiftTypesController.show","parameters":[{"description":"Sona unique identifier of the shift_type","in":"path","name":"shift_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShiftType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual shift type","tags":["ShiftTypes"]},"put":{"description":"Update a Shift Type","operationId":"BackendWeb.ApiV1.ShiftTypesController.update","parameters":[{"description":"The Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"The shift type parameters","in":"body","name":"shift_type","required":false,"schema":{"$ref":"#/definitions/ShiftTypeUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShiftType"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found: the shift type doesn't exist in your organisation, or the ID is not a valid UUID.","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a field fails validation; `{\"error\", \"message\"}` with `org_unit not found` when `org_unit_id` is given but not in your organisation. `org_unit_id` and `type_of_work_id` can't be changed by this endpoint.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a Shift Type","tags":["ShiftTypes"]}},"/api/v1/shift_types/{shift_type_id}/pay_variants":{"get":{"description":"List all pay variants associated with a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.index","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/ShiftTypePayVariants"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Paginated list of pay variants","tags":["PayVariants"]},"post":{"description":"Creates a pay variant associated with a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.create","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Pay variant creation parameters for the shift type","in":"body","name":"pay_variant","required":false,"schema":{"$ref":"#/definitions/ShiftTypePayVariantParams"}}],"responses":{"201":{"description":"Success","schema":{"$ref":"#/definitions/ShiftTypePayVariant"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"A shift type or pay variant ID is not a valid UUID (`{error, message}`), or the pay variant fails validation (`{errors}`)","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Creates a pay variant","tags":["PayVariants"]}},"/api/v1/shift_types/{shift_type_id}/pay_variants/{pay_variant_id}":{"delete":{"description":"Deletes a pay variant associated with a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.delete","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Sona unique identifier of the pay variant","in":"path","name":"pay_variant_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Deletes a pay variant","tags":["PayVariants"]},"get":{"description":"Gets a pay variant associated with a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.show","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Sona unique identifier of the pay variant","in":"path","name":"pay_variant_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShiftTypePayVariant"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets a pay variant","tags":["PayVariants"]},"put":{"description":"Updates a pay variant associated with a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.update","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Sona unique identifier of the pay variant","in":"path","name":"pay_variant_id","required":true,"type":"string"},{"description":"Pay variant updatable parameters for the shift type","in":"body","name":"pay_variant","required":false,"schema":{"$ref":"#/definitions/ShiftTypePayVariantParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShiftTypePayVariant"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"A shift type or pay variant ID is not a valid UUID (`{error, message}`), or the pay variant fails validation (`{errors}`)","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Updates a pay variant","tags":["PayVariants"]}},"/api/v1/shift_types/{shift_type_id}/pay_variants/{pay_variant_id}/link":{"delete":{"description":"Unlinks a pay variant from a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.unlink","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Sona unique identifier of the pay variant","in":"path","name":"pay_variant_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Unlinks a pay variant from a shift type","tags":["PayVariants"]},"post":{"description":"Links a pay variant to a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.PayVariantsController.link","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"Sona unique identifier of the pay variant","in":"path","name":"pay_variant_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Links a pay variant to a shift type","tags":["PayVariants"]}},"/api/v1/shift_types/{shift_type_id}/time_and_attendance_policy":{"delete":{"description":"Deletes the time and attendance policy for a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.TimeAndAttendancePolicyController.delete","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"}],"responses":{"204":{"description":"Success"},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: the `shift_type_id` is not a valid UUID (\"Invalid shift_type_id\").","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Deletes a shift type's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"get":{"description":"Returns the time and attendance policy of a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.TimeAndAttendancePolicyController.show","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Gets a shift type's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"post":{"description":"Creates the time and attendance policy for a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.TimeAndAttendancePolicyController.create","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"The shift type's T&A policy parameters","in":"body","name":"time_and_attendance_policy","required":true,"schema":{"$ref":"#/definitions/TimeAndAttendancePolicyParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a policy field fails validation; `{\"error\", \"message\"}` for an invalid `shift_type_id` (\"Invalid shift_type_id\"), a policy that is not an object (\"Invalid time_and_attendance_policy\") or missing parameters.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Creates a shift type's time and attendance policy","tags":["TimeAndAttendancePolicy"]},"put":{"description":"Updates the time and attendance policy for a shift type.","operationId":"BackendWeb.ApiV1.ShiftTypes.TimeAndAttendancePolicyController.update","parameters":[{"description":"Sona unique identifier of the shift type","in":"path","name":"shift_type_id","required":true,"type":"string"},{"description":"The shift type's T&A policy parameters","in":"body","name":"time_and_attendance_policy","required":true,"schema":{"$ref":"#/definitions/TimeAndAttendancePolicyParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TimeAndAttendancePolicy"}},"401":{"description":"Unauthorised","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"errors\": ...}` when a policy field fails validation; `{\"error\", \"message\"}` for an invalid `shift_type_id` (\"Invalid shift_type_id\"), a policy that is not an object (\"Invalid time_and_attendance_policy\") or missing parameters.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}}},"summary":"Updates a shift type's time and attendance policy","tags":["TimeAndAttendancePolicy"]}},"/api/v1/shifts":{"get":{"description":"A shift represents a method of accounting for a specific block of time. The most common usage is to represent a period of work to be performed. The vast majority of shifts created in the Sona platform are represented by the category `open_shifts`. The `shift_type` represents the sort of work being performed which is in turn linked to `type_of_work` which governs which users are able to see and work the shift. `min_start_datetime` and `max_start_datetime` are required and the longest timespan shifts can be listed for is 7 days.","operationId":"BackendWeb.ApiV1.ShiftsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Comma separated list of org unit IDs","in":"query","name":"org_unit_ids","required":false,"type":"string"},{"description":"The earliest shift start date time to return in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"min_start_datetime","required":true,"type":"string"},{"description":"The latest shift start date time to return in the following ISO 8601 UTC format: 2019-04-20T07:47:58Z (`YYYY-MM-DDTHH:mm:SSZ`)","in":"query","name":"max_start_datetime","required":true,"type":"string"},{"description":"The user_id to filter shifts by","in":"query","name":"user_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Shifts"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of shifts","tags":["Shifts"]},"post":{"description":"Create a new shift.\n\nWhen overlapping employment periods are enabled, a regular worker's shift belongs to one\nemployment period. Submit it as `employment_period.id` (find IDs with\n`GET /api/v1/employment_periods`). When it is omitted and exactly one of the worker's\nemployment periods covers the shift and holds the shift type's type of work, that one is\nstored. When several do, the request fails with an error on `employment_period_id`.\n","operationId":"BackendWeb.ApiV1.ShiftsController.create","parameters":[{"description":"The shift parameters","in":"body","name":"shift","required":false,"schema":{"$ref":"#/definitions/ShiftParameters"}}],"responses":{"200":{"description":"Success, shift created","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to create a shift with these parameters. `message` is an object mapping field names to lists of messages for field validation failures, or a string for rejections such as an unsupported category or an onboarding user.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new shift","tags":["Shifts"]}},"/api/v1/shifts/{shift_id}":{"get":{"description":"Get an individual shift. See the shifts listing endpoint for more details on shifts.","operationId":"BackendWeb.ApiV1.ShiftsController.show","parameters":[{"description":"Sona unique identifier of the shift","in":"path","name":"shift_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual shift","tags":["Shifts"]},"put":{"description":"Update an individual shift.\n\nWhen overlapping employment periods are enabled, an update keeps the shift's employment\nperiod while it still covers the shift and holds the shift type's type of work. Otherwise,\nor when a shift without one changes its dates, worker or shift type, the employment period\nis resolved as on create: submit `employment_period.id` when several employment periods\ncould hold the shift. The employment period of a shift in a shift run cannot be changed\nthrough the API.\n","operationId":"BackendWeb.ApiV1.ShiftsController.update","parameters":[{"description":"Sona unique identifier of the shift","in":"path","name":"shift_id","required":true,"type":"string"},{"description":"The shift parameters","in":"body","name":"shift","required":false,"schema":{"$ref":"#/definitions/ShiftParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A shift with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not update a shift with these parameters. `message` is an object mapping field names to lists of messages for field validation failures, or a string for rejections such as an unsupported category or an onboarding user.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an individual shift","tags":["Shifts"]}},"/api/v1/shifts/{shift_id}/assign_to_agency":{"put":{"description":"Assign an individual shift to an agency. If none of `agency_worker_id`, `agency_worker_first_name` or `agency_worker_last_name` are provided (omitted, null or empty), the shift is assigned to the agency without a specific worker.","operationId":"BackendWeb.ApiV1.ShiftsController.assign_to_agency","parameters":[{"description":"Sona unique identifier of the shift","in":"path","name":"shift_id","required":true,"type":"string"},{"description":"Details of the worker and agency to assign the shift to","in":"body","name":"agency_assignment","required":false,"schema":{"properties":{"agency_id":{"description":"The Sona unique identifier of the agency","type":"string"},"agency_worker_first_name":{"description":"If no agency worker id is provided we will try and find a worker with the given first name and last name at the provided agency, if no such user is found, they will be created. Ignored if `agency_worker_id` is provided.","type":"string","x-nullable":true},"agency_worker_id":{"description":"The Sona unique identifier of the agency worker. If omitted, null or empty along with `agency_worker_first_name` and `agency_worker_last_name`, the shift is assigned to the agency without a specific worker yet.","type":"string","x-nullable":true},"agency_worker_last_name":{"description":"If no agency worker id is provided we will try and find a worker with the given first name and last name at the provided agency, if no such user is found, they will be created. Ignored if `agency_worker_id` is provided.","type":"string","x-nullable":true}},"required":["agency_id"],"type":"object"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The shift or agency was not found (`{error, message}`). Validation failures while assigning are also returned as 404, with a `{errors}` body (known issue: they should be 422).","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"422":{"description":"Could not assign shift to agency","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Assign an individual shift to an agency","tags":["Shifts"]}},"/api/v1/shifts/{shift_id}/cancel":{"put":{"description":"Cancel an individual shift","operationId":"BackendWeb.ApiV1.ShiftsController.cancel","parameters":[{"description":"Sona unique identifier of the shift","in":"path","name":"shift_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A shift with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not cancel a shift with these parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Cancel an individual shift","tags":["Shifts"]}},"/api/v1/shifts/{shift_id}/unassign_from_agency":{"post":{"description":"Unassign an individual shift from an agency.","operationId":"BackendWeb.ApiV1.ShiftsController.unassign_from_agency","parameters":[{"description":"Sona unique identifier of the shift","in":"path","name":"shift_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Shift"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A shift with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not unassign shift from agency","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Unassign an individual shift from an agency","tags":["Shifts"]}},"/api/v1/skills_and_attributes":{"get":{"description":"Get a list of skills and attributes","operationId":"BackendWeb.ApiV1.SkillsAndAttributesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/SkillsAndAttributes"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of skills and attributes","tags":["SkillsAndAttributes"]},"post":{"description":"Create new skills and attributes","operationId":"BackendWeb.ApiV1.SkillsAndAttributesController.create","parameters":[{"description":"The skill attribute parameters","in":"body","name":"skillattribute","required":false,"schema":{"$ref":"#/definitions/SkillAttributeCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SkillAttribute"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity: `{\"error\", \"message\"}`, where `message` is an object of field errors when validation fails, or a string for other rejections.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create new skills and attributes","tags":["SkillsAndAttributes"]}},"/api/v1/skills_and_attributes/{skill_attribute_id_or_idx}/assign_to_user":{"post":{"description":"Use this endpoint to assign a skill or an attribute to a user. Skills can be given optional start and end dates.","operationId":"BackendWeb.ApiV1.SkillsAndAttributesController.assign_to_user","parameters":[{"description":"A Sona unique identifier or an external unique identifier of the skill or attribute that is being assigned.","in":"path","name":"skill_attribute_id_or_idx","required":true,"type":"string"},{"description":"The parameters of the request","in":"body","name":"skill_attribute_assignment_data","required":false,"schema":{"$ref":"#/definitions/AssignToUserParameters"}}],"responses":{"201":{"description":"Success","schema":{"example":{"end_date":"2025-06-30","skill_attribute_id":"9925c220-a290-464a-9f7e-16f60cd70052","start_date":"2025-01-01","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"end_date":{"description":"The optional date the skill remains valid to","type":"string","x-nullable":true},"skill_attribute_id":{"description":"The identifier of the skill or attribute that was assigned","type":"string"},"start_date":{"description":"The optional date the skill starts","type":"string","x-nullable":true},"user_id":{"description":"The identifier of the user the skill or attribute was assigned to","type":"string"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found: `{\"errors\": {\"user_id\": [\"not found\"]}}` or `{\"errors\": {\"skill_attribute_id\": [\"not found\"]}}` when the user or skill/attribute doesn't exist in the organisation (field errors are deliberately returned as 404); `{\"error\", \"message\"}` for other missing resources.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Assigns a skill or an attribute to a user","tags":["SkillsAndAttributes"]}},"/api/v1/skills_and_attributes/{skill_attribute_id_or_idx}/retract":{"post":{"description":"Use this endpoint to remove a skill or an attribute from the user.","operationId":"BackendWeb.ApiV1.SkillsAndAttributesController.retract","parameters":[{"description":"A Sona unique identifier or an external unique identifier of the skill or attribute that is being retracted.","in":"path","name":"skill_attribute_id_or_idx","required":true,"type":"string"},{"description":"The parameters of the request","in":"body","name":"skill_attribute_assignment_data","required":false,"schema":{"$ref":"#/definitions/RetractFromUserParameters"}}],"responses":{"200":{"description":"Success","schema":{"example":{"end_date":"2025-06-30","skill_attribute_id":"9925c220-a290-464a-9f7e-16f60cd70052","start_date":"2025-01-01","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"end_date":{"description":"The optional date the skill remains valid to","type":"string","x-nullable":true},"skill_attribute_id":{"description":"The identifier of the skill or attribute that was removed","type":"string"},"start_date":{"description":"The optional date the skill starts","type":"string","x-nullable":true},"user_id":{"description":"The identifier of the user the skill or attribute was removed","type":"string"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Take back assigned skill or attribute from the user","tags":["SkillsAndAttributes"]}},"/api/v1/skills_and_attributes/{skill_attribute_id_or_idx}/update_assignment":{"put":{"description":"Use this endpoint to update the start date and/or end date of an existing skill or attribute assignment for a user.","operationId":"BackendWeb.ApiV1.SkillsAndAttributesController.update_assignment","parameters":[{"description":"A Sona unique identifier or an external unique identifier of the skill or attribute.","in":"path","name":"skill_attribute_id_or_idx","required":true,"type":"string"},{"description":"The parameters of the request","in":"body","name":"update_assignment_data","required":false,"schema":{"$ref":"#/definitions/UpdateAssignmentParameters"}}],"responses":{"200":{"description":"Success","schema":{"example":{"end_date":"2025-06-30","skill_attribute_id":"9925c220-a290-464a-9f7e-16f60cd70052","start_date":"2025-01-01","user_id":"b9d1a3ad-a3db-4150-81c1-33c8c613271a"},"properties":{"end_date":{"description":"The updated date the skill remains valid to","type":"string","x-nullable":true},"skill_attribute_id":{"description":"The identifier of the skill or attribute that was updated","type":"string"},"start_date":{"description":"The date the skill starts","type":"string","x-nullable":true},"user_id":{"description":"The identifier of the user","type":"string"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found: `{\"errors\": {\"user_id\": [\"not found\"]}}` or `{\"errors\": {\"skill_attribute_id\": [\"not found\"]}}` when the user or skill/attribute doesn't exist in the organisation (field errors are deliberately returned as 404); `{\"error\", \"message\"}` for other missing resources.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an assigned skill or attribute for a user","tags":["SkillsAndAttributes"]}},"/api/v1/status_records":{"get":{"description":"Every person's status history, newest first, for keeping a copy in sync: pass `updated_at` to get only records created or changed since your last sync, including a record being ended by a newer one. Read only. Requires demand-based rostering to be enabled for your organisation (403 otherwise).","operationId":"BackendWeb.ApiV1.StatusRecordsController.index_for_org","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"`active` or `inactive`","in":"query","name":"status","required":false,"type":"string"},{"description":"Records that started on or after this date (YYYY-MM-DD, UTC)","in":"query","name":"from","required":false,"type":"string"},{"description":"Records that started on or before this date (YYYY-MM-DD, UTC)","in":"query","name":"to","required":false,"type":"string"},{"description":"Records created or changed on or after this time, YYYY-MM-DDThh:mm:ssZ in UTC (e.g. 2026-01-15T10:00:00Z)","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/StatusRecordDataList"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Feature not enabled","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"List status records across your organisation","tags":["PeopleWeSupport"]}},"/api/v1/termination_reasons":{"get":{"description":"List the termination reasons configured for the organisation. These are the values accepted by the `termination_reason_id` field on the employment periods endpoints, so this endpoint is the source of truth for mapping a termination reason name to its Sona id.","operationId":"BackendWeb.ApiV1.TerminationReasonsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by whether the termination reason is archived. If omitted, both archived and live termination reasons are returned.","in":"query","name":"archived","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/TerminationReasons"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Termination Reasons","tags":["TerminationReasons"]}},"/api/v1/termination_reasons/{termination_reason_id}":{"get":{"description":"Get an individual termination reason. See the termination reason listing endpoint for a description of termination reasons.","operationId":"BackendWeb.ApiV1.TerminationReasonsController.show","parameters":[{"description":"Sona unique identifier of the termination reason","in":"path","name":"termination_reason_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TerminationReason"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual termination reason","tags":["TerminationReasons"]}},"/api/v1/training_course_attendances":{"get":{"description":"List all training course attendances for the current organisation.","operationId":"BackendWeb.ApiV1.TrainingCourseAttendancesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The Sona unique identifier of the user","in":"query","name":"user_id","required":false,"type":"string"},{"description":"The external id of the user","in":"query","name":"user_idx","required":false,"type":"string"},{"description":"The Sona unique identifier of the training course","in":"query","name":"training_course_id","required":false,"type":"string"},{"description":"The external id of the training course","in":"query","name":"training_course_idx","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/TrainingCourseAttendances"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Training Course Attendances","tags":["TrainingCourseAttendances"]},"post":{"description":"Create a new training course attendance.","operationId":"BackendWeb.ApiV1.TrainingCourseAttendancesController.create","parameters":[{"description":"The course attendance parameters","in":"body","name":"training_course_attendance","required":false,"schema":{"$ref":"#/definitions/CreateAttendanceParameters"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/TrainingCourseAttendance"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The user or training course could not be resolved: `user_id`/`user_idx` or `training_course_id`/`training_course_idx` is missing, invalid, or matches no record (or the id and idx match different records): `{\"error\", \"message\"}` with a string `message`. Field validation failures return `{\"errors\": {...}}` instead.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new training course attendance","tags":["TrainingCourseAttendances"]}},"/api/v1/training_course_attendances/{attendance_id_or_idx}":{"delete":{"description":"Delete a training course attendance.","operationId":"BackendWeb.ApiV1.TrainingCourseAttendancesController.delete","parameters":[{"description":"The Sona unique identifier or external id of the training course attendance","in":"path","name":"attendance_id_or_idx","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete a training course attendance","tags":["TrainingCourseAttendances"]},"get":{"description":"Get an individual training course attendance.","operationId":"BackendWeb.ApiV1.TrainingCourseAttendancesController.show","parameters":[{"description":"The Sona unique identifier or external id of the training course attendance","in":"path","name":"attendance_id_or_idx","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourseAttendance"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual training course attendance","tags":["TrainingCourseAttendances"]},"put":{"description":"Update a training course attendance.","operationId":"BackendWeb.ApiV1.TrainingCourseAttendancesController.update","parameters":[{"description":"The Sona unique identifier or external id of the training course attendance","in":"path","name":"attendance_id_or_idx","required":true,"type":"string"},{"description":"The course attendance parameters","in":"body","name":"training_course_attendance","required":false,"schema":{"$ref":"#/definitions/UpdateAttendanceParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourseAttendance"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No training course attendance with this id or idx","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The attendance parameters failed validation","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a training course attendance","tags":["TrainingCourseAttendances"]}},"/api/v1/training_courses":{"get":{"description":"List all training courses for the current organisation.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"Filter by whether courses are mandatory or not. If omitted all courses are returned.","in":"query","name":"mandatory","required":false,"type":"string"},{"description":"Filter by course name. If omitted all courses are returned.","in":"query","name":"name","required":false,"type":"string"},{"description":"Filter by course status (archived or live). If omitted all courses are returned.","in":"query","name":"archived","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/TrainingCourses"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"`mandatory` or `archived` is not 'true' or 'false'","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Training Courses","tags":["TrainingCourses"]},"post":{"description":"Create a new training course.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.create","parameters":[{"description":"The course parameters","in":"body","name":"course","required":false,"schema":{"$ref":"#/definitions/CreateCourseParameters"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/TrainingCourse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid parameters. `mandatory` not 'true' or 'false', or a `frequency` or `type` outside the allowed values, return `{error, message}`; missing or invalid fields caught by validation return `{errors}`.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new training course","tags":["TrainingCourses"]}},"/api/v1/training_courses/{course_id_or_idx}":{"get":{"description":"Get an individual training course.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.show","parameters":[{"description":"The Sona unique identifier or external id of the training course","in":"path","name":"course_id_or_idx","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual training course","tags":["TrainingCourses"]},"put":{"description":"Update a training course.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.update","parameters":[{"description":"Sona unique identifier or external id of the course to update","in":"path","name":"course_id_or_idx","required":true,"type":"string"},{"description":"The course parameters","in":"body","name":"course","required":false,"schema":{"$ref":"#/definitions/UpdateCourseParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No training course with this id or idx","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The course parameters failed validation","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a training course","tags":["TrainingCourses"]}},"/api/v1/training_courses/{course_id_or_idx}/archive":{"put":{"description":"Archive a training course.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.archive","parameters":[{"description":"Sona unique identifier or external id of the course to archive","in":"path","name":"course_id_or_idx","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No training course with this id or idx","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Archive a training course","tags":["TrainingCourses"]}},"/api/v1/training_courses/{course_id_or_idx}/unarchive":{"put":{"description":"Unarchive a training course that was previously archived.","operationId":"BackendWeb.ApiV1.TrainingCoursesController.unarchive","parameters":[{"description":"Sona unique identifier or external id of the course to unarchive","in":"path","name":"course_id_or_idx","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingCourse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No training course with this id or idx","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Turn an archived course back to live","tags":["TrainingCourses"]}},"/api/v1/training_venues":{"post":{"description":"Creates a training venue: a rosterable org unit under the given parent with the organisation's 'Training venue' org unit type, its location (coordinates from latitude and longitude when given, else converted from a what3words reference when given, else derived from the postcode; sending both coordinates and a reference is refused), the organisation-wide 'Training' type of work (reused when one exists) and a 'Training' shift type at the venue linked to it. Everything is created in one transaction: an address that fails validation leaves nothing behind.","operationId":"BackendWeb.ApiV1.TrainingVenuesController.create","parameters":[{"description":"The training venue parameters","in":"body","name":"training_venue","required":false,"schema":{"$ref":"#/definitions/TrainingVenueCreateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TrainingVenue"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Service Unavailable — the postcode lookup timed out and nothing was saved; retry the same request","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a training venue","tags":["TrainingVenues"]}},"/api/v1/types_of_work":{"get":{"description":"A type of work defines a kind of work a user may be qualified to do or not. The users who can claim or be assigned to a shift are defined based on matching the type of work associated with the shift type to the types of work a user has associated with them","operationId":"BackendWeb.ApiV1.TypesOfWorkController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/TypesOfWork"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of Types Of Work","tags":["TypesOfWork"]}},"/api/v1/types_of_work/{type_of_work_id}":{"get":{"description":"Get an individual type of work","operationId":"BackendWeb.ApiV1.TypesOfWorkController.show","parameters":[{"description":"Sona unique identifier of the type of work","in":"path","name":"type_of_work_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TypeOfWork"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual type of work","tags":["TypesOfWork"]}},"/api/v1/users":{"get":{"description":"A user in Sona typically represents an entity that can login to one or more components of the system. For example the manager portal or the mobile application. Additionally there are some headless users who cannot login, for example agency workers. Users by default have no access to anything and must be explicitly granted access to roles and organisational units.\n\n**Pagination**: Results are paginated. Use the `page` and `page_size` query parameters to navigate. The default page size is 10. Note: the parameter name is `page` (not `page_number`). The response includes `page_number`, `page_size`, `total_pages`, and `total_entries` metadata to assist with iteration. Example: `GET /api/v1/users?page=2&page_size=100`","operationId":"BackendWeb.ApiV1.UsersController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The external id to filter by, useful for checking if a user with the given external id exists before deciding whether to create or update","in":"query","name":"external_id","required":false,"type":"string"},{"description":"The employee number to filter by (often a payroll id)","in":"query","name":"employee_number","required":false,"type":"string"},{"description":"The employee contact email to filter by","in":"query","name":"contact_email","required":false,"type":"string"},{"description":"The employee email to filter by, returns a matching employee for either contact or work email","in":"query","name":"email","required":false,"type":"string"},{"description":"The type of users to return (`agency` or `non_agency`), if omitted all users are returned","enum":["agency","non_agency"],"in":"query","name":"type","required":false,"type":"string"},{"description":"The state of users to return (one of the values below), if omitted all users are returned","enum":["active","deactivated","leaver","left","starting_soon","onboarding"],"in":"query","name":"state","required":false,"type":"string"},{"description":"Specify ordering with format 'field[direction]' where direction is 'asc' or 'desc' (e.g., 'updated_at[desc]'). If omitted, users are ordered by first_name, last_name for deterministic results","in":"query","name":"order","required":false,"type":"string"},{"description":"Users who have their last day of employment on this date, if omitted the default order is returned","in":"query","name":"last_day_of_employment","required":false,"type":"string"},{"description":"The insurance number to filter by","in":"query","name":"insurance_number","required":false,"type":"string"},{"description":"Comma separated list of org unit identifiers (Sona internal identifiers or external identifiers). Only returns users with an administrative site relationship to the specified org units.","in":"query","name":"org_unit_ids","required":false,"type":"string"},{"description":"UTC datetime to filter users updated on or after this timestamp. Use format YYYY-MM-DDThh:mm:ssZ in UTC (e.g. 2024-01-15T10:00:00Z).","in":"query","name":"updated_at","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Users"},"page_number":{"description":"The current page number (1-indexed)","type":"integer"},"page_size":{"description":"The number of items per page (default: 10)","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of users","tags":["Users"]},"post":{"description":"Create a new user. Some profile fields may become mandatory depending on your organisation's configuration. For example, `contact_email` is required when the `make_fields_mandatory` setting is enabled for your organisation.","operationId":"BackendWeb.ApiV1.UsersController.create","parameters":[{"description":"The user parameters","in":"body","name":"user","required":false,"schema":{"$ref":"#/definitions/CreateUserParameters"}}],"responses":{"200":{"description":"Success, user created","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to create a user with these parameters. `message` is usually an array of error strings (e.g. `[\"insurance_number Must be a valid national insurance number.\"]`), or a single string for request-level errors.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new user","tags":["Users"]}},"/api/v1/users/lookup":{"get":{"description":"Resolve **exactly one** user using **exactly one** of: `insurance_number`, `employee_number`, `external_id`, `contact_email`, `email`. Returns that user, their employment periods (up to 200), and `lookup.by`. HTTP status codes are documented under **Responses**.","operationId":"BackendWeb.ApiV1.UsersController.lookup","parameters":[{"description":"UK NI number (case-insensitive). One of five identity params; use only one.","in":"query","name":"insurance_number","required":false,"type":"string"},{"description":"Employment period employee number. One of five identity params; use only one.","in":"query","name":"employee_number","required":false,"type":"string"},{"description":"Organisation user `external_employee_id`. One of five identity params; use only one.","in":"query","name":"external_id","required":false,"type":"string"},{"description":"User contact email. One of five identity params; use only one.","in":"query","name":"contact_email","required":false,"type":"string"},{"description":"Contact or work email. One of five identity params; use only one.","in":"query","name":"email","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UserLookupResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Lookup user and periods","tags":["User lookup"]}},"/api/v1/users/{user_id_or_idx}/terminate":{"put":{"description":"Terminate a user's employment. This is the correct endpoint for triggering the full termination workflow — simply setting `final_date_of_employment` via `PUT /users/{id}` will not change the user's state.\n\nAfter termination, the user's state is derived automatically based on `final_date_of_employment`: `leaver` if the date is today or in the future, `left` if in the past.\n\n**Organisations with the overlapping employment periods feature:** termination ends every contract running on `final_date_of_employment`. Each of those employment periods gets that date as its `finish_date` and the same termination fields. A contract that already ended before that date keeps its own `finish_date`. If any of the person's employment periods starts after `final_date_of_employment`, the termination is rejected and nothing changes. To end one contract while the others continue, set a `finish_date` on its current employment period with `PUT /api/v1/employment_periods/{employment_period_id}` instead.","operationId":"BackendWeb.ApiV1.UsersController.terminate","parameters":[{"description":"Sona unique identifier of the user or external id","in":"path","name":"user_id_or_idx","required":true,"type":"string"},{"description":"Termination parameters","in":"body","name":"user","required":false,"schema":{"$ref":"#/definitions/TerminateUserParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TerminatedUserResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"API token does not have access to this endpoint — contact Sona to update permissions","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A user with that ID or external ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Termination failed — invalid parameters, missing final_date_of_employment, or leaver workflow not enabled. Returns `ErrorResponse`, except field-level validation failures, which return `ValidationErrors`.","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Terminates an individual user's employment","tags":["Users"]}},"/api/v1/users/{user_id_or_idx}/undo_termination":{"put":{"description":"Undo a previously processed termination, restoring the user to active status.","operationId":"BackendWeb.ApiV1.UsersController.undo_termination","parameters":[{"description":"Sona unique identifier of the user or external id","in":"path","name":"user_id_or_idx","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"API token does not have access to this endpoint — contact Sona to update permissions","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Undo termination of an individual user's employment","tags":["Users"]}},"/api/v1/users/{user_id}":{"get":{"description":"Get an individual user","operationId":"BackendWeb.ApiV1.UsersController.show","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"user_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual user","tags":["Users"]},"put":{"description":"Update an individual user","operationId":"BackendWeb.ApiV1.UsersController.update","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"user_id","required":true,"type":"string"},{"description":"The user parameters","in":"body","name":"user","required":false,"schema":{"$ref":"#/definitions/UpdateUserParameters"}}],"responses":{"200":{"description":"Success, user updated","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A user with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not update a user with these parameters. `message` is usually an array of error strings, or a single string for request-level errors.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an individual user","tags":["Users"]}},"/api/v1/users/{user_id}/activate":{"put":{"description":"Activate an individual user","operationId":"BackendWeb.ApiV1.UsersController.activate","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"user_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A user with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not activate a user with these parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Activate an individual user","tags":["Users"]}},"/api/v1/users/{user_id}/deactivate":{"put":{"description":"Deactivate an individual user","operationId":"BackendWeb.ApiV1.UsersController.deactivate","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"user_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"A user with that ID was not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Could not deactivate a user with these parameters","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Deactivate an individual user","tags":["Users"]}},"/api/v1/users/{user_id}/standard_documents/right_to_work":{"get":{"description":"List right to work documents for the current user","operationId":"BackendWeb.ApiV1.RightToWorkDocumentsController.index","parameters":[{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"},{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/RightToWorkDocuments"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"An ID in the path is not a valid UUID. `message` is an object mapping each invalid parameter to an error string, e.g. `{\"id\": \"Invalid UUID format: 'abc'.\"}`.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of right to work documents","tags":["RightToWorkDocuments"]}},"/api/v1/users/{user_id}/standard_documents/right_to_work/":{"post":{"consumes":["multipart/form-data"],"description":"Create a new right to work document entry","operationId":"BackendWeb.ApiV1.RightToWorkDocumentsController.create","parameters":[{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"},{"description":"List of files to upload. The total upload size must not exceed 100 MB.","in":"formData","name":"file_uploads","required":false,"type":"file"},{"description":"The expiration date of the document","format":"date","in":"formData","name":"expiry_date","required":false,"type":"string"},{"description":"The issue date of the document","format":"date","in":"formData","name":"issue_date","required":false,"type":"string"},{"description":"The date when document was checked","format":"date","in":"formData","name":"date_checked","required":false,"type":"string"},{"description":"The type of the document","enum":["passport","visa","dbs","driving_license","eu_settlement","ilr","sia","share_code","student_visa","work_permit","external_id_check","skilled_worker_visa"],"in":"formData","name":"document_type","required":true,"type":"string"},{"description":"ISO 3166 Alpha-2 code for the country of issue of the document","in":"formData","name":"country_of_issue","required":false,"type":"string"},{"description":"ISO 3166 Alpha-2 code for the nationality in the document","in":"formData","name":"nationality","required":false,"type":"string"},{"description":"Share code","in":"formData","name":"share_code","required":false,"type":"string"},{"description":"COS field 1","in":"formData","name":"cos_field_1","required":false,"type":"string"},{"description":"COS field 2","in":"formData","name":"cos_field_2","required":false,"type":"string"},{"description":"Additional notes about document","in":"formData","name":"notes","required":false,"type":"string"}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/RightToWorkDocument"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No user with this ID exists in the organisation","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Validation failed. Field validation errors (for example an unknown `country_of_issue` or a missing date) return `{\"errors\": {...}}`. An ID that is not a valid UUID returns `{\"error\", \"message\"}` with `message` mapping each invalid parameter to an error string. An expiry date before the issue date or a failed file upload returns `{\"error\", \"message\"}` with a string `message`.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new right to work document","tags":["RightToWorkDocuments"]}},"/api/v1/users/{user_id}/standard_documents/right_to_work/{id}":{"delete":{"description":"Delete a right to work document entry.","operationId":"BackendWeb.ApiV1.RightToWorkDocumentsController.delete","parameters":[{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"},{"description":"Sona unique identifier of the right to work document entry","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"An ID in the path is not a valid UUID. `message` is an object mapping each invalid parameter to an error string.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete a right to work document entry","tags":["RightToWorkDocuments"]},"get":{"description":"Get data about a single right to work document for a user.","operationId":"BackendWeb.ApiV1.RightToWorkDocumentsController.show","parameters":[{"description":"The Sona unique identifier of the right to work document.","in":"path","name":"id","required":true,"type":"string"},{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/RightToWorkDocument"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"An ID in the path is not a valid UUID. `message` is an object mapping each invalid parameter to an error string, e.g. `{\"id\": \"Invalid UUID format: 'abc'.\"}`.","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a single right to work document.","tags":["RightToWorkDocuments"]},"put":{"consumes":["multipart/form-data"],"description":"Update a right to work document entry","operationId":"BackendWeb.ApiV1.RightToWorkDocumentsController.update","parameters":[{"description":"The Sona unique identifier of the user.","in":"path","name":"user_id","required":true,"type":"string"},{"description":"Sona unique identifier of the right to work document entry","in":"path","name":"id","required":true,"type":"string"},{"description":"List of files to upload. The total upload size must not exceed 20 MB. Will overwrite existing files.","in":"formData","name":"file_uploads","required":false,"type":"file"},{"description":"The expiration date of the document","format":"date","in":"formData","name":"expiry_date","required":false,"type":"string"},{"description":"The issue date of the document","format":"date","in":"formData","name":"issue_date","required":false,"type":"string"},{"description":"The date when document was checked","format":"date","in":"formData","name":"date_checked","required":false,"type":"string"},{"description":"The type of the document","enum":["passport","visa","dbs","driving_license","eu_settlement","ilr","sia","share_code","student_visa","work_permit","external_id_check","skilled_worker_visa"],"in":"formData","name":"document_type","required":false,"type":"string"},{"description":"ISO 3166 Alpha-2 code for the country of issue of the document","in":"formData","name":"country_of_issue","required":false,"type":"string"},{"description":"ISO 3166 Alpha-2 code for the nationality in the document","in":"formData","name":"nationality","required":false,"type":"string"},{"description":"Share code","in":"formData","name":"share_code","required":false,"type":"string"},{"description":"COS field 1","in":"formData","name":"cos_field_1","required":false,"type":"string"},{"description":"COS field 2","in":"formData","name":"cos_field_2","required":false,"type":"string"},{"description":"Additional notes about document","in":"formData","name":"notes","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/RightToWorkDocument"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No right to work document with this ID exists for the user","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Validation failed. Field validation errors return `{\"errors\": {...}}`. An ID that is not a valid UUID returns `{\"error\", \"message\"}` with `message` mapping each invalid parameter to an error string. A failed file upload returns `{\"error\", \"message\"}` with a string `message`.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a right to work document","tags":["RightToWorkDocuments"]}},"/api/v1/users/{users_id}/auth_strategies":{"get":{"description":"List all authentication strategies for the given user","operationId":"BackendWeb.ApiV1.AuthStrategiesController.index","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"users_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UserAuthStrategies"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of auth strategies for a user","tags":["AuthStrategies"]}},"/api/v1/users/{users_id}/auth_strategies/{id}":{"delete":{"description":"Delete an authentication strategy for the given user","operationId":"BackendWeb.ApiV1.AuthStrategiesController.delete","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"users_id","required":true,"type":"string"},{"description":"Sona unique identifier of the auth strategy","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"No Content"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"`users_id` or `id` is not a valid UUID, e.g. `Invalid UUID format for id: 'abc'`","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete an individual user auth strategy","tags":["AuthStrategies"]},"get":{"description":"Get an individual authentication strategy for the given user","operationId":"BackendWeb.ApiV1.AuthStrategiesController.show","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"users_id","required":true,"type":"string"},{"description":"Sona unique identifier of the auth strategy","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UserAuthStrategy"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get an individual user auth strategy","tags":["AuthStrategies"]},"put":{"description":"Update an existing authentication strategy for the given user","operationId":"BackendWeb.ApiV1.AuthStrategiesController.update","parameters":[{"description":"Sona unique identifier of the user","in":"path","name":"users_id","required":true,"type":"string"},{"description":"Sona unique identifier of the auth strategy","in":"path","name":"id","required":true,"type":"string"},{"description":"The auth strategy parameters","in":"body","name":"auth_strategy","required":false,"schema":{"$ref":"#/definitions/UserAuthStrategyUpdateParams"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UserAuthStrategy"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update an individual user auth strategy","tags":["AuthStrategies"]}},"/api/v1/webhooks/subscriptions":{"get":{"description":"A Webhook Subscription allows API clients to get notified when an event occurs.","operationId":"BackendWeb.ApiV1.SubscriptionsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/Subscriptions"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of subscriptions","tags":["Webhook Subscriptions"]},"post":{"description":"Create a new subscription for supplied event type","operationId":"BackendWeb.ApiV1.SubscriptionsController.create","parameters":[{"description":"The subscription parameters","in":"body","name":"subscription","required":false,"schema":{"$ref":"#/definitions/SubscriptionParameters"}}],"responses":{"201":{"description":"Success, subscription created","schema":{"$ref":"#/definitions/Subscription"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unable to create a subscription with these parameters","schema":{"properties":{"errors":{"example":{"event_type":["is invalid"],"url":["is missing a scheme (e.g. https)"]},"properties":{"event_type":{"items":{"type":"string"},"type":"array"},"url":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a new subscription","tags":["Webhook Subscriptions"]}},"/api/v1/webhooks/subscriptions/{id}":{"delete":{"description":"Delete a webhook subscription by its ID","operationId":"BackendWeb.ApiV1.SubscriptionsController.delete","parameters":[{"description":"The subscription ID","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"Success, subscription deleted"},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Subscription not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Delete a subscription","tags":["Webhook Subscriptions"]}},"/api/v1/work_restrictions":{"get":{"description":"Returns a list of work restrictions sorted by creation date. Allows filtering by `worker_id`.","operationId":"BackendWeb.ApiV1.WorkRestrictionsController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The worker ID to filter by","in":"query","name":"worker_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/WorkRestrictions"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid pagination or filter parameters. `message` maps each invalid parameter to its error messages","schema":{"$ref":"#/definitions/DetailedErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of work restrictions","tags":["WorkRestrictions"]},"post":{"description":"Creates a work restriction for a worker","operationId":"BackendWeb.ApiV1.WorkRestrictionsController.create","parameters":[{"description":"Work restriction parameters","in":"body","name":"body","required":false,"schema":{"$ref":"#/definitions/CreateWorkRestrictionParameters"}}],"responses":{"200":{"description":"Created, or updated if the worker already has work restrictions (this endpoint upserts and returns 200, not 201)","schema":{"$ref":"#/definitions/WorkRestriction"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No worker with this `worker_id` in the organisation. Unusually, the body is the field-validation shape: `{\"errors\": {\"worker_id\": [\"not found\"]}}`","schema":{"$ref":"#/definitions/ValidationErrors"}},"422":{"description":"`worker_id` is missing or not a valid UUID, or a flag is not a boolean","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a work restriction","tags":["WorkRestrictions"]}},"/api/v1/work_restrictions/{work_restriction_id}":{"get":{"description":"Returns a work restriction by ID","operationId":"BackendWeb.ApiV1.WorkRestrictionsController.show","parameters":[{"description":"The ID of the work restriction to get","in":"path","name":"work_restriction_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/WorkRestriction"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The `id` is not a valid UUID","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a work restriction by ID","tags":["WorkRestrictions"]}},"/api/v1/work_restrictions/{worker_id}":{"put":{"description":"Updates a work restriction for the specified worker. The `worker_id` parameter should be the worker's user ID.","operationId":"BackendWeb.ApiV1.WorkRestrictionsController.update","parameters":[{"description":"The ID of the worker whose work restriction to update","in":"path","name":"worker_id","required":true,"type":"string"},{"description":"Work restriction parameters","in":"body","name":"body","required":false,"schema":{"$ref":"#/definitions/UpdateWorkRestrictionParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/WorkRestriction"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The worker has no work restrictions yet (create them with POST). Unusually, the body has only `error` and no `message`: `{\"error\": \"Work restriction not found for this worker. Use POST to create.\"}`","schema":{"$ref":"#/definitions/MetricsErrorResponse"}},"422":{"description":"The worker ID is not a valid UUID, or a flag is not a boolean","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a work restriction","tags":["WorkRestrictions"]}},"/api/v1/working_preferences":{"get":{"description":"Returns a list of working preferences sorted by creation date. Allows filtering by `worker_id`.","operationId":"BackendWeb.ApiV1.WorkingPreferencesController.index","parameters":[{"description":"Number of elements per page","in":"query","minimum":1,"name":"page_size","required":false,"type":"integer"},{"description":"Number of the page","in":"query","minimum":1,"name":"page","required":false,"type":"integer"},{"description":"The worker ID to filter by","in":"query","name":"worker_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"properties":{"entries":{"$ref":"#/definitions/WorkingPreferences"},"page_number":{"description":"The page number","type":"integer"},"page_size":{"description":"The number of items per page","type":"integer"},"total_entries":{"description":"The total number of entities available","type":"integer"},"total_pages":{"description":"The total number of pages available","type":"integer"}},"type":"object"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Invalid query parameters. A `worker_id` that isn't a UUID, or a non-integer `page`/`page_size`, returns `{errors}`; pagination values outside the allowed range return `{error, message}` with `message` as an object of field errors.","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a list of working preferences","tags":["WorkingPreferences"]},"post":{"description":"Creates a working preference","operationId":"BackendWeb.ApiV1.WorkingPreferencesController.create","parameters":[{"description":"Working preference parameters","in":"body","name":"body","required":false,"schema":{"$ref":"#/definitions/CreateWorkingPreferenceParameters"}}],"responses":{"200":{"description":"Created. The status is 200, not 201.","schema":{"$ref":"#/definitions/WorkingPreference"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The worker was not found: `{\"errors\": {\"worker_id\": [\"not found\"]}}`","schema":{"$ref":"#/definitions/ValidationErrors"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Create a working preference","tags":["WorkingPreferences"]}},"/api/v1/working_preferences/{worker_id}":{"put":{"description":"Updates a working preference for the specified worker. The `worker_id` parameter should be the worker's user ID.","operationId":"BackendWeb.ApiV1.WorkingPreferencesController.update","parameters":[{"description":"The ID of the worker whose working preference to update","in":"path","name":"worker_id","required":true,"type":"string"},{"description":"Working preference parameters","in":"body","name":"body","required":false,"schema":{"$ref":"#/definitions/UpdateWorkingPreferenceParameters"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/WorkingPreference"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"The worker has no working preference yet; create one with POST. Unlike `ErrorResponse`, the body has only `error`.","schema":{"example":{"error":"Working preference not found for this worker. Use POST to create."},"properties":{"error":{"description":"What went wrong","type":"string"}},"required":["error"],"type":"object"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a working preference","tags":["WorkingPreferences"]}},"/api/v1/working_preferences/{working_preference_id}":{"get":{"description":"Returns a working preference by ID","operationId":"BackendWeb.ApiV1.WorkingPreferencesController.show","parameters":[{"description":"The ID of the working preference to get","in":"path","name":"working_preference_id","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/WorkingPreference"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The working preference ID is invalid (for example not a UUID)","schema":{"$ref":"#/definitions/ValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a working preference by ID","tags":["WorkingPreferences"]}},"/api/v1/yapster/users/{external_employee_id}":{"post":{"consumes":["application/json"],"description":"Returns all the information required by the Yapster client to migrate a user from the Yapster app to the Sona app.","operationId":"BackendWeb.ApiV1.YapsterMigrationController.migration_info","parameters":[{"description":"Customer employee ID","in":"path","name":"external_employee_id","required":true,"type":"string"},{"description":"Yapster User confirmed phone numbers","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/YapsterUserMigrationInfoRequestBody"}}],"produces":["application/json"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/YapsterUserMigrationInfoResponse"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get a user's Yapster migration info","tags":["YapsterMigration"]}},"/api/v1/yearly_holiday_allowance":{"get":{"description":"Get user's holiday allowance for a year","operationId":"BackendWeb.ApiV1.YearlyHolidayAllowanceController.show","parameters":[{"description":"Sona unique identifier of the user to get the holiday allowance","in":"query","name":"user_id","required":true,"type":"string"},{"description":"The start date holiday year for which to set the entitlement in the following ISO 8601 UTC format: 2019-04-20 (`YYYY-MM-DD`)","in":"query","name":"start_date","required":false,"type":"string"},{"description":"The Sona unique identifier of the contract the request is about, from `contract_id` on `GET /api/v1/employment_periods`. Only used by organisations with overlapping employment periods enabled. Required when the user holds employment periods on several contracts covering the requested date; without it the only contract covering that date is used. A contract whose employment periods have ended still reads its own absence year covering the date.","in":"query","name":"contract_id","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/YearlyHolidayAllowanceList"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"Unprocessable Entity","schema":{"$ref":"#/definitions/ErrorResponse"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Get user's holiday allowance for a year","tags":["YearlyHolidayAllowance"]},"post":{"description":"Update a user's holiday allowance for a year","operationId":"BackendWeb.ApiV1.YearlyHolidayAllowanceController.update","parameters":[{"description":"The holiday allowance parameters","in":"body","name":"holiday_allowance","required":false,"schema":{"$ref":"#/definitions/YearlyHolidayAllowanceParameters"}}],"responses":{"200":{"description":"Success, absence updated","schema":{"$ref":"#/definitions/YearlyHolidayAllowance"}},"401":{"description":"The Bearer token was missing or not valid. Returned by every endpoint before the request is processed.","examples":{"application/json":{"error":"Unauthorised","message":"The Bearer token was either missing or not valid"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"The API token is valid but restricted to other endpoints. Ask Sona to add this endpoint to the token's allowed list.","examples":{"application/json":{"error":"Forbidden","message":"This API credential does not have access to this endpoint"}},"schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/ErrorResponse"}},"422":{"description":"The allowance can't be set. Request-level problems return `{error, message}` with a string `message` (e.g. the user isn't employed on `start_date`, `contract_id` doesn't apply, or `start_date` isn't a date); field validation failures return `{errors}`","schema":{"$ref":"#/definitions/ErrorOrValidationErrors"}},"500":{"description":"Server Error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"summary":"Update a user's holiday allowance for a year","tags":["YearlyHolidayAllowance"]}}},"produces":["application/json"],"security":[{"Bearer":[]}],"securityDefinitions":{"Bearer":{"description":"Enter your token prefixed with 'Bearer '","in":"header","name":"Authorization","type":"apiKey"}},"swagger":"2.0","tags":[{"description":"Resolve a single user from an external identifier (National Insurance number, employee number, external employee id, or email) and return bundled related data. Additional endpoints in this group may be added over time.","name":"User lookup"}]}