Holds & schedule requests
Pencil a date onto the schedule before the work is won, turn it into a real visit, and handle teammates' requests for a date, a move or a skipped day.
View as MarkdownTwo ways to get work onto the schedule that the Schedule & crews endpoints don't cover: holds, for dates you want to keep free before a job is priced or won, and schedule requests, where someone who can't edit the schedule (or doesn't want to) asks the people who can.
Writes are made as the person who created the API key, with their own TruTec permissions, and need operations turned on for the workspace (403 Operations not enabled otherwise). Holds and approvals need permission to edit the schedule (403 view_only otherwise); anyone in operations can make a request.
Holds#
A hold is a tentative visit: a job name, an address, a line of scope and a date (and optionally a crew and times). With a crew, the hold locks those crew days so nothing else is scheduled there. A hold never publishes to crews, is never dispatched, and never tells the customer.
Holds are work orders with is_hold: true, so GET /v1/work-orders?include_holds=true and GET /v1/work-orders/{id} read them too. The hold object is the work order object plus:
| Field | Type | Description |
|---|---|---|
locked_days |
array | Crew days this hold blocks. Empty without a crew. |
job_is_placeholder |
boolean | The job was made for the hold. Linking the hold to the real job merges it away. |
List holds#
GET /v1/schedule/holds
Permission: Read jobs
Takes start_date, end_date (a hold is included when any of its days falls in the range), crew_id, job_id, limit and offset. Ordered by date.
Hold a date#
POST /v1/schedule/holds
Permission: Write jobs
| Field | Type | Required | Description |
|---|---|---|---|
scheduled_date |
string | Yes | The day to hold. |
job_id |
integer | One of job_id or name |
Hold the date for an existing job. |
name |
string | One of job_id or name |
Customer or job name. Makes a lightweight job in the first stage of your sales pipeline, so it stays out of the schedule queue until sales moves it. |
scope |
string | No | What the work is. The first line becomes the hold's title. |
address |
string | No | Defaults to the job's address. |
estimated_days |
number | No | Days on site, 0.5 to 365. Default 1. |
start_time, end_time |
string | No | Local times like "07:00". A finish before the start runs past midnight. |
crew_id |
integer | No | Locks this crew's days. |
override_lock |
boolean | No | Owners only: hold a crew day that is already locked. |
curl -X POST "https://trutec.ai/api/v1/schedule/holds" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "name": "Ridgeview HOA", "address": "400 Ridgeview Pkwy, Austin, TX", "scope": "Mill and overlay, phase 1", "scheduled_date": "2026-11-09", "estimated_days": 3, "crew_id": 18 }'
Returns 201 with the hold as data, job_created (a placeholder job was made) and same_day_work_order_ids (other work already on that crew day: a heads-up, not a block). A crew day that is already held or locked returns 409 with the locks in the way. Workspaces whose jobs come from an outside system must pass job_id.
Change a hold#
PATCH /v1/schedule/holds/{id}
Permission: Write jobs
Takes title, scope, address, scheduled_date, estimated_days, start_time, end_time, crew_id and override_lock. Only the fields sent change. Moving it or changing its crew re-checks the locks (409 when that crew day is taken) and moves its locks along.
Turn a hold into a visit#
POST /v1/schedule/holds/{id}/convert
Permission: Write jobs
The work was won: the hold becomes a real visit on the same day and crew, and its locks are released. It is a draft until you publish the schedule, like any new visit. Returns the work order.
Tie a hold to the real job#
POST /v1/schedule/holds/{id}/link with {"job_id": 70412}
Permission: Write jobs
The hold moves onto that job and keeps its date, crew, times and locks. When the hold sat on a placeholder job made for it (and nothing else uses it), the placeholder is merged into the real job, its notes, activity and files included, and removed; merged_placeholder_job names it.
Release a hold#
DELETE /v1/schedule/holds/{id}
Permission: Write jobs and Delete records
Lets the date go: the hold and its crew-day locks are removed. The job stays. Returns 204.
Schedule requests#
A request asks the people who can edit the schedule for one of three things:
kind |
Asks for | Needs |
|---|---|---|
date |
A date for a whole job, or one service line of it | job_id, requested_date, optionally estimate_service_id |
move |
Moving a scheduled visit to another date | work_order_id, requested_date |
skip |
One day of a multi-day visit off (not its first day) | work_order_id, skip_date |
Approving does exactly what the scheduler's own action would. Everyone who can edit the schedule gets an in-app notification of a new request; the requester gets one when it is decided. Nothing reaches customers or crews until the schedule is published.
The request object#
| Field | Type | Description |
|---|---|---|
id |
integer | Request id. |
kind |
string | date, move or skip. |
status |
string | pending, approved or declined. |
summary |
string | One plain line, for example "Move Sealcoat: Wed Nov 4 → Thu Nov 5". |
job_id, job |
integer, object | The job. |
requested_date, skip_date |
string | The date asked for (for skip, both are the day off). |
estimate_service_id, service_name |
The service line, for a date request about one line. |
|
work_order_id, work_order_title |
The visit, for move and skip. |
|
note |
string or null | The requester's note. Approving adds it to the visit's dispatch notes. |
requested_by, decided_by |
object or null | {id, name}. |
decided_at, decision_note, created_at |
List requests#
GET /v1/schedule/date-requests
Permission: Read jobs
Takes status, kind, job_id, work_order_id, limit and offset. Newest first.
Make a request#
POST /v1/schedule/date-requests
Permission: Write jobs
Takes the fields in the table above plus note. Service lines that can be requested are the job's unscheduled lines (estimate_service_id from GET /v1/schedule/queue); a line already on the calendar is 400 (ask to move its visit instead). A request still waiting for the same thing is 409.
curl -X POST "https://trutec.ai/api/v1/schedule/date-requests" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "kind": "move", "work_order_id": 4120, "requested_date": "2026-11-05", "note": "Tenant asked for Thursday" }'
Returns 201 with the request.
Approve or decline#
POST /v1/schedule/date-requests/{id}/approve
POST /v1/schedule/date-requests/{id}/decline
Permission: Write jobs
Both take an optional note the requester sees. Approve places the work on the date, moves the visit or skips the day, with the same crew-day locks and rules as a drag on the Schedule (409 when one gets in the way), and returns the request plus the work_order it changed. Decline changes nothing on the schedule. A request that was already decided is 409.