Schedule
Placing work: assign employees to jobs, move jobs in time, and find double bookings. Every placement is checked against the employee's other jobs, absences, working hours and employment.
Assign an employee to a work order
/schedule/assignSends an employee to a job, and optionally places the job in time in the same request. This is how an integration schedules work.
Before writing, the employee is checked at the job's (new) times against their other jobs, absences, availability rules and employment dates:
- Errors — a double booking (another non-cancelled job of theirs overlaps; back-to-back is fine) or a date outside their employment — refuse the request with 409
schedule_conflictand the findings. Nothing is written. Send the same request withforce: trueto book anyway. - Warnings — outside their availability rules, or during an absence — never block. The assignment is written and the warnings come back in
conflicts.
A job with no times is not checked: assigning somebody to unplanned work reserves them before the day is agreed.
Effects: a booked job that has a start moves to scheduled. Assigning the same employee again updates their assignment (role) instead of adding a second. A new assignment notifies the employee by email — and by SMS if the organization has turned that on — and sets notified_at. A work order or employee that is not your organization's is a 404.
curl -X POST 'https://api.closepact.com/v1/api/schedule/assign' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"role": "lead",
"planned_start": "2026-11-03T08:00:00+01:00",
"planned_end": "2026-11-03T12:00:00+01:00"
}'{
"data": {
"id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"order_number": "1042",
"title": "Flyttstädning – Storgatan 12, Uppsala",
"description": "Tre rum och kök, 78 m². Kunden lämnar nycklarna kl. 08.00.",
"status": "scheduled",
"priority": 1,
"source": "manual",
"handover": null,
"customer": {
"id": "01a15c02-4e1a-7b3c-9d2e-5f60a7b8c901",
"name": "Brf Linden",
"customer_number": null
},
"pricing": {
"model": "hourly",
"agreed_price_cents": null,
"currency": "SEK",
"rot_rut": "rut",
"rot_rut_share": 100
},
"schedule": {
"planned_start": "2026-11-03T07:00:00+00:00",
"planned_end": "2026-11-03T11:00:00+00:00",
"earliest_start": "2026-11-03T07:00:00+00:00",
"latest_start": "2026-11-03T09:00:00+00:00",
"estimated_minutes": 240,
"actual_start": null,
"actual_end": null,
"actual_minutes": null
},
"assignments": [
{
"id": "01a15c11-0b1c-7f20-b162-93a4ebf10d45",
"role": "lead",
"status": "assigned",
"notified_at": "2026-10-28T13:02:11+00:00",
"accepted_at": null,
"employee": {
"id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"name": "Ali Hassan",
"color": "#2F80ED"
}
}
],
"completed_at": null,
"cancelled_at": null,
"cancel_reason": null,
"customer_signed_at": null,
"created_at": "2026-10-28T13:01:55+00:00",
"updated_at": "2026-10-28T13:02:11+00:00"
},
"conflicts": [
{
"code": "outside_working_hours",
"severity": "warning",
"message": "Ali Hassan does not normally work those hours.",
"context": {
"hours": [
{
"start": "07:00:00",
"end": "11:30:00"
},
{
"start": "12:15:00",
"end": "16:00:00"
}
]
}
}
],
"assignment_id": "01a15c11-0b1c-7f20-b162-93a4ebf10d45"
}Request body
application/json · required
work_order_idstring(uuid)requiredThe work order.
employee_idstring(uuid)requiredThe employee to send.
rolestringDefault
lead.One of
leadhelperplanned_startstring(date-time)nullablePlace the job at this time as well (otherwise its current times are used). ISO 8601 date-time. An offset is honoured (
2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.planned_endstring(date-time)nullableEnd of that time; after
planned_start. ISO 8601 date-time. An offset is honoured (2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.forcebooleanWrite even if the check finds errors. Default false.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredA job (uppdrag): what is to be done, for which customer, where and when, and by whom. Which nested parts are included depends on the endpoint — see each property.
idstring(uuid)requiredThe work order id.
order_numberstringrequiredNumber from the organization's work order series, unique within the organization (format set in the panel, e.g.
1042orWO-01042).titlestringrequiredWhat the job is, as shown on the board.
descriptionstringnullablerequiredFree text for the people doing the job.
statusstringrequiredbooked: promised to the customer, nobody placed yet.scheduled: somebody is going.in_progress: they are there.completed: the work is done (and billable).invoiced: the money has been asked for.cancelled: not happening.One of
bookedscheduledin_progresscompletedinvoicedcancelledpriorityintegerrequired-2 (lowest) to 2 (highest); 0 is normal.
-2–2
sourcestringrequiredWhere the job came from:
manual(created in the panel or this API),recurrence(written by a recurrence) orquote(made from an accepted quote — seehandover). More values may be added.handoverobjectnullablerequiredThe accepted quote this work order was made from, or null when it was not.
customerobjectrequiredThe customer the job is for (a customer from the Customers API). Still shown after that customer is deleted: a deleted customer keeps its past jobs.
contactobjectnullableThe customer contact for this job, or null. Included by
GET /work-orders/{workOrder}only.service_addressobjectnullableWhere the work is done, or null. Included by the list,
GET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete.pricingobjectrequiredscheduleobjectrequiredWhen the job is planned, what was promised, and when it actually ran. All UTC.
assignmentsarray of objectsWho is going. Included everywhere except the response to creating a work order.
servicesarray of objectsServices ordered for the job, priced as they were when ordered. Included by
GET /work-orders/{workOrder}only.checklistarray of objectsThe steps to tick, in
sort_order. Included byGET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete.materialsarray of objectsMaterials used. Included by
GET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete;GET /work-orders/{workOrder}/materialsreturns the same lines withnet_cents.completed_atstring(date-time)nullablerequiredWhen the job was completed, or null.
cancelled_atstring(date-time)nullablerequiredWhen the job was cancelled, or null.
cancel_reasonstringnullablerequiredThe
reasongiven when cancelling, or null.customer_signed_atstring(date-time)nullablerequiredWhen the customer signed on completion, or null.
created_atstring(date-time)requiredISO 8601 date-time, in UTC.
updated_atstring(date-time)requiredISO 8601 date-time, in UTC.
conflictsarray of objectsrequiredWarnings (and, with
force, errors) that were overridden. Empty when the slot is clean.assignment_idstring(uuid)requiredThe assignment, for unassigning later.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Move a work order to a new time
/schedule/moveMoves a job to new planned times, keeping everybody on it. Each assigned employee is checked at the new times exactly as when assigning: errors refuse the move with 409 schedule_conflict unless force is true, warnings come back in conflicts. The status does not change. A work order that is not your organization's is a 404.
curl -X POST 'https://api.closepact.com/v1/api/schedule/move' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"planned_start": "2026-11-04T08:00:00+01:00",
"planned_end": "2026-11-04T12:00:00+01:00"
}'{
"data": {
"id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"order_number": "1042",
"title": "Flyttstädning – Storgatan 12, Uppsala",
"description": "Tre rum och kök, 78 m². Kunden lämnar nycklarna kl. 08.00.",
"status": "scheduled",
"priority": 1,
"source": "manual",
"handover": null,
"customer": {
"id": "01a15c02-4e1a-7b3c-9d2e-5f60a7b8c901",
"name": "Brf Linden",
"customer_number": null
},
"pricing": {
"model": "hourly",
"agreed_price_cents": null,
"currency": "SEK",
"rot_rut": "rut",
"rot_rut_share": 100
},
"schedule": {
"planned_start": "2026-11-04T07:00:00+00:00",
"planned_end": "2026-11-04T11:00:00+00:00",
"earliest_start": "2026-11-03T07:00:00+00:00",
"latest_start": "2026-11-03T09:00:00+00:00",
"estimated_minutes": 240,
"actual_start": null,
"actual_end": null,
"actual_minutes": null
},
"assignments": [
{
"id": "01a15c11-0b1c-7f20-b162-93a4ebf10d45",
"role": "lead",
"status": "assigned",
"notified_at": "2026-10-28T13:02:11+00:00",
"accepted_at": null,
"employee": {
"id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01",
"name": "Ali Hassan",
"color": "#2F80ED"
}
}
],
"completed_at": null,
"cancelled_at": null,
"cancel_reason": null,
"customer_signed_at": null,
"created_at": "2026-10-28T13:01:55+00:00",
"updated_at": "2026-10-28T13:02:11+00:00"
},
"conflicts": []
}Request body
application/json · required
work_order_idstring(uuid)requiredThe work order.
planned_startstring(date-time)requiredNew start. ISO 8601 date-time. An offset is honoured (
2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.planned_endstring(date-time)requiredNew end; after
planned_start. ISO 8601 date-time. An offset is honoured (2026-11-03T08:00:00+01:00is stored as 07:00 UTC); a value without one is read as UTC. Responses are always UTC.forcebooleanWrite even if the check finds errors. Default false.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredA job (uppdrag): what is to be done, for which customer, where and when, and by whom. Which nested parts are included depends on the endpoint — see each property.
idstring(uuid)requiredThe work order id.
order_numberstringrequiredNumber from the organization's work order series, unique within the organization (format set in the panel, e.g.
1042orWO-01042).titlestringrequiredWhat the job is, as shown on the board.
descriptionstringnullablerequiredFree text for the people doing the job.
statusstringrequiredbooked: promised to the customer, nobody placed yet.scheduled: somebody is going.in_progress: they are there.completed: the work is done (and billable).invoiced: the money has been asked for.cancelled: not happening.One of
bookedscheduledin_progresscompletedinvoicedcancelledpriorityintegerrequired-2 (lowest) to 2 (highest); 0 is normal.
-2–2
sourcestringrequiredWhere the job came from:
manual(created in the panel or this API),recurrence(written by a recurrence) orquote(made from an accepted quote — seehandover). More values may be added.handoverobjectnullablerequiredThe accepted quote this work order was made from, or null when it was not.
customerobjectrequiredThe customer the job is for (a customer from the Customers API). Still shown after that customer is deleted: a deleted customer keeps its past jobs.
contactobjectnullableThe customer contact for this job, or null. Included by
GET /work-orders/{workOrder}only.service_addressobjectnullableWhere the work is done, or null. Included by the list,
GET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete.pricingobjectrequiredscheduleobjectrequiredWhen the job is planned, what was promised, and when it actually ran. All UTC.
assignmentsarray of objectsWho is going. Included everywhere except the response to creating a work order.
servicesarray of objectsServices ordered for the job, priced as they were when ordered. Included by
GET /work-orders/{workOrder}only.checklistarray of objectsThe steps to tick, in
sort_order. Included byGET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete.materialsarray of objectsMaterials used. Included by
GET /work-orders/{workOrder}andPOST /work-orders/{workOrder}/complete;GET /work-orders/{workOrder}/materialsreturns the same lines withnet_cents.completed_atstring(date-time)nullablerequiredWhen the job was completed, or null.
cancelled_atstring(date-time)nullablerequiredWhen the job was cancelled, or null.
cancel_reasonstringnullablerequiredThe
reasongiven when cancelling, or null.customer_signed_atstring(date-time)nullablerequiredWhen the customer signed on completion, or null.
created_atstring(date-time)requiredISO 8601 date-time, in UTC.
updated_atstring(date-time)requiredISO 8601 date-time, in UTC.
conflictsarray of objectsrequiredFindings for the people on the job at the new time.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
List conflicts on the schedule
/schedule/conflictsEvery problem on the board in a window: each planned job that overlaps the window and is not cancelled, completed or invoiced is checked for each employee on it, exactly as when assigning. Read-only — use it for a morning check or to show a warning banner. A double booking between two jobs is reported once from each side.
curl 'https://api.closepact.com/v1/api/schedule/conflicts?from=2026-11-02T00%3A00%3A00Z&to=2026-11-09T00%3A00%3A00Z' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": [
{
"code": "double_booked",
"severity": "error",
"message": "Ali Hassan is already on 1042 then.",
"context": {
"work_order_id": "01a15c10-8a2c-7f70-b162-93a4ebf10d45",
"order_number": "1043",
"title": "Fönsterputs – Kungsgatan 4, Uppsala",
"planned_start": "2026-11-03T08:00:00+00:00",
"planned_end": "2026-11-03T09:00:00+00:00"
},
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"order_number": "1042",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01"
},
{
"code": "double_booked",
"severity": "error",
"message": "Ali Hassan is already on 1042 then.",
"context": {
"work_order_id": "01a15c10-8a2b-7e6f-a051-8293dae0fc34",
"order_number": "1042",
"title": "Flyttstädning – Storgatan 12, Uppsala",
"planned_start": "2026-11-03T07:00:00+00:00",
"planned_end": "2026-11-03T11:00:00+00:00"
},
"work_order_id": "01a15c10-8a2c-7f70-b162-93a4ebf10d45",
"order_number": "1043",
"employee_id": "01a15b90-1f2e-7a3b-8c4d-5e6f7a8b9c01"
}
]
}Query parameters
fromstring(date-time)requiredWindow start. ISO 8601 date or date-time; a date means midnight UTC.
Example
2026-11-02T00:00:00Ztostring(date-time)requiredWindow end, after
from. ISO 8601 date or date-time.Example
2026-11-09T00:00:00Z
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataarray of objectsrequiredcodestringrequireddouble_booked(error): already on another non-cancelled job that overlaps; jobs that only touch (one ends 12:00, the next starts 12:00) do not.not_employed(error): inactive, or outsidestarted_at–ended_at(both days included).absent(warning): an absence that is not rejected overlaps.outside_working_hours(warning): the employee has availability rules and the job falls outside them. All butdouble_bookedare judged on the Swedish clock: the job's UTC times are converted toEurope/Stockholmbefore they are compared with working hours, absences and employment dates.One of
double_bookednot_employedabsentoutside_working_hoursseveritystringrequiredWhether it blocks.
One of
errorwarningmessagestringrequiredExplanation, in English, naming the employee.
contextobjectrequiredDetails by code.
double_booked:work_order_id,order_number,title,planned_start,planned_endof the other job.absent:absence_id,type,starts_at,ends_at.outside_working_hours:weekday(no hours that day) orhours(the day'sstart/endpairs).not_employed:status,started_at,ended_at.work_order_idstring(uuid)requiredThe job checked.
order_numberstringrequiredIts number.
employee_idstring(uuid)requiredThe employee checked.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Take an employee off a work order
/work-orders/{workOrder}/assignments/{assignment}Removes one assignment. A scheduled job left with nobody on it falls back to booked, so it shows up again among the work that needs placing. The employee is not notified. An assignment of another job is a 404.
curl -X DELETE 'https://api.closepact.com/v1/api/work-orders/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/assignments/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"deleted": true
}Path parameters
workOrderstring(uuid)requiredThe work order id.
assignmentstring(uuid)requiredThe assignment id, as
assignments[].idon the work order orassignment_idfromPOST /schedule/assign.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
deletedbooleanrequiredOne of
true
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 429The key has made more requests this minute than its limit allows.Errors