Task schedules
A task schedule, called a task rule in the app, creates tasks on its own. A reservation rule creates them from each stay at its properties, for example a turnover clean 60 minutes after every check-out. A calendar rule creates them on a repeat, on empty days between stays, or on days inside a stay. This guide covers the six task schedule operations. Every field is listed on the operation’s page in the API Reference.
| Operation | Request | Permission |
|---|---|---|
listTaskSchedules | GET /task-schedules | view_tasks |
createReservationTaskSchedule | POST /task-schedules/reservation | modify_tasks |
createRecurringTaskSchedule | POST /task-schedules/recurring | modify_tasks |
updateTaskSchedule | PATCH /task-schedules/{id} | modify_tasks |
previewTaskScheduleActivation | GET /task-schedules/{id}/activation-preview | view_tasks |
toggleTaskSchedule | POST /task-schedules/{id}/toggle | modify_tasks |
Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. See Authentication for permissions. The API can’t delete a task rule. Pause it instead.
How a rule is built
Section titled “How a rule is built”A rule has three layers:
scopes: where it applies, at least one entry. Each entry has ascopeTypeofproperty,property_grouportagand exactly one matching ID:propertyId,propertyGroupIdortagId(a property tag fromlistTags). A group or tag also covers properties added to it later.workflows: when it fires, at least one. A reservation rule takes reservation workflows only. A calendar rule takes repeat, vacancy or occupancy workflows, and can mix them.itemsinside each workflow: the tasks it creates, at least one. Each occurrence creates one task per item.
Each item needs a departmentId (from listDepartments) and either a templateId (from listTemplates) or a plain taskName, never both. priority is watch, low, medium (the default), high or urgent. assignmentMode decides who gets each task:
assignmentMode | Assignee |
|---|---|
unassigned (default) | Nobody |
specific | assigneeUserId, the person’s userId from listTeamMembers, or assigneeOrgConnectionId |
auto | The department’s auto-assignment picks someone |
property_default | The property’s default person |
assigneeUserId and assigneeOrgConnectionId are rejected with any mode other than specific. collaborators adds people to every task as userIds, and is not allowed with a company assignee.
Reservation rules
Section titled “Reservation rules”POST /task-schedules/reservation creates a rule driven by stays. Each workflow names an eventAnchor (check_in, check_out or confirmation, when the booking is made) and an offsetMinutes from it, between -525600 and 525600. A negative offset is before the anchor: -1440 is one day before check-in.
curl -X POST https://api-us.suiteop.com/api/v1/task-schedules/reservation \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 4b7e2c9a-1f3d-4e8b-a6c5-9d0f2e1b3a78" \ -H "Content-Type: application/json" \ -d '{ "name": "Turnover clean after every check-out", "scopes": [{ "scopeType": "property_group", "propertyGroupId": "2e6d0f5b-8a1c-4d3e-9b7f-6c0a2e4d8f13" }], "stayMinNights": 2, "workflows": [ { "eventAnchor": "check_out", "offsetMinutes": 60, "items": [ { "templateId": "9a3c5e7f-1b2d-4f6a-8c0e-3d5b7f9a1c24", "departmentId": "0b6f2c1e-4d3a-4f8b-9c2e-7a1d5e6f8b90", "priority": "high", "assignmentMode": "auto" } ] } ] }'It answers 201 with the rule, paused:
{ "data": { "id": "6e1f3a9c-2d4b-4c8e-9f7a-0b5d3e1c8a26", "name": "Turnover clean after every check-out", "description": null, "type": "reservation", "isActive": false, "autoAssign": false, "stayMinNights": 2, "stayMaxNights": null, "sourceRuleType": null, "bookingChannels": null, "createdAt": "2026-10-11T14:02:18.000Z", "updatedAt": "2026-10-11T14:02:18.000Z", "scopes": [ { "id": "c4a8e2f6-0b1d-4e3a-9c5f-7d2b6e0a4c18", "scopeType": "property_group", "propertyId": null, "propertyGroupId": "2e6d0f5b-8a1c-4d3e-9b7f-6c0a2e4d8f13", "tagId": null } ], "workflows": [ { "id": "f2b6d0a4-8c1e-4f3b-a7d9-5e0c2a6b8d31", "triggerType": "reservation_event", "name": null, "rank": 0, "eventAnchor": "check_out", "offsetMinutes": 60, "variant": "every", "linkNextStayMaxGapNights": null, "items": [ { "id": "8d0f2b4a-6c3e-4a1d-9b7f-1e5c3a9d7b42", "templateId": "9a3c5e7f-1b2d-4f6a-8c0e-3d5b7f9a1c24", "taskName": null, "departmentId": "0b6f2c1e-4d3a-4f8b-9c2e-7a1d5e6f8b90", "priority": "high", "rank": 0, "assignmentMode": "auto", "assigneeUserId": null, "assigneeOrgConnectionId": null, "collaborators": null } ] } ] }, "meta": { "requestId": "…" }}Every workflow object carries the fields of all four kinds. The example leaves out the calendar fields, which are null or their defaults on a reservation workflow.
variant(check-out only):every(the default) fires on every departure,turnonly when a guest arrives the same day, andno_turnonly when nobody does.linkNextStayMaxGapNights(check-out only): link the task to the next stay only when it arrives within this many nights,0for a same-day turn. Omit it to link whatever arrives next.- Stay conditions:
stayMinNightsandstayMaxNights(1 to 365) limit which stays fire the rule.bookingChannelswithsourceRuleType(only_selectedorall_except_selected) limits it by channel, such as["airbnb", "vrbo"]. Send both channel fields or neither.
Calendar rules
Section titled “Calendar rules”POST /task-schedules/recurring creates a rule driven by dates rather than stays. Each workflow’s kind is set by the keys you send, and sending keys of two kinds in one workflow is refused:
| Kind | Marked by | Fires |
|---|---|---|
| Repeat | recurrenceType | daily, weekly (on daysOfWeek, 0 = Sunday) or monthly (on dayOfMonth), every recurrenceInterval periods |
| Vacancy | vacancyMinGapDays | On day vacancyCadenceDays of each empty gap between stays that matches the gap condition, then every vacancyCadenceDays days through the gap. vacancyRepeats: false fires once per gap |
| Occupancy | occupancyCadenceDays | On day occupancyCadenceDays of each stay that matches the stay-length condition, then every occupancyCadenceDays days through the stay, for example a mid-stay clean. occupancyRepeats: false fires once per stay |
A repeat workflow needs timeOfDay (HH:MM, 24-hour) and a startDate (YYYY-MM-DD). timeOfDay is read on each property’s own clock, its timezone from getProperty, so a 09:00 rule over properties in New York and Denver fires at 09:00 local in each. The workflow’s own timezone field is deprecated and has no effect, but keep sending a valid IANA zone such as America/New_York (on update, the one the rule already stores): an older API revision that can still serve your request requires it.
curl -X POST https://api-us.suiteop.com/api/v1/task-schedules/recurring \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Idempotency-Key: 1c8e4a2f-7b3d-4d9e-8f6a-2b0c5e7d9a13" \ -H "Content-Type: application/json" \ -d '{ "name": "Monday pool check", "scopes": [{ "scopeType": "property", "propertyId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }], "workflows": [ { "recurrenceType": "weekly", "daysOfWeek": [1], "timeOfDay": "09:00", "timezone": "America/New_York", "startDate": "2026-10-12", "rescheduleIfOccupied": true, "rescheduleTarget": "fully_vacant", "rescheduleWindowDays": 3, "rescheduleFallback": "keep_original", "items": [{ "taskName": "Check pool chemistry", "departmentId": "5c2e8a1f-9b3d-4a6e-8f7c-0d1b2e3f4a5b" }] } ] }'- Rescheduling around guests: with
rescheduleIfOccupied: true, an occurrence that lands on an occupied day moves to a nearbyfully_vacant,check_out_only,check_in_onlyorturnday withinrescheduleWindowDays.rescheduleFallbackdecides what happens when there is none:skiporkeep_original. Send all three with the flag. - Seasons:
startDateandendDatebound when a workflow may fire. WithrepeatAnnually: trueonly the month and day count, so2026-11-01to2027-03-31means every winter. - No duplicates: two repeat workflows that create the same task at the same moment, such as “every day 09:30” and “Mondays 09:30”, are refused with
400 validation_error. On create the keyvalidation.recurrence_overlapis in an issue’smessage; on update it is inerror.message. The same task at two different times of day is allowed. - Times come back with seconds. A
timeOfDayyou send as"09:00"reads back as"09:00:00".
Previewing and starting a rule
Section titled “Previewing and starting a rule”Both create operations save the rule paused unless you send startPaused: false. A paused rule creates nothing. Check it before you start it:
curl "https://api-us.suiteop.com/api/v1/task-schedules/6e1f3a9c-2d4b-4c8e-9f7a-0b5d3e1c8a26/activation-preview" \ -H "Authorization: Bearer sk_live_your_key_here"{ "data": { "reservationCount": 38, "countIsUpperBound": false, "blockers": [] }, "meta": { "requestId": "…" }}reservationCountis how many upcoming stays would get tasks. It is always0for a calendar rule.countIsUpperBound: truemeans aturnorno_turnworkflow will fire on only some of them.blockerslists what is wrong with the rule’s setup, as message keys. Not every blocker stops it creating tasks: an item whose template was deleted is skipped while the rest still run, and an item with no assignee still creates its task, unassigned.
| Blocker key | Meaning |
|---|---|
validation.at_least_one_workflow | A calendar rule has no workflow |
validation.at_least_one_item | No workflow has an item |
validation.schedule_template_deleted | An item’s template was deleted |
validation.specific_assignee_required | An item is specific but has no assignee |
validation.schedule_no_properties | The rule no longer covers any property |
validation.recurrence_cannot_generate | A repeat can never fire, such as weekly with no weekday |
Then start it:
curl -X POST "https://api-us.suiteop.com/api/v1/task-schedules/6e1f3a9c-2d4b-4c8e-9f7a-0b5d3e1c8a26/toggle" \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{"isActive": true}'{ "data": { "id": "6e1f3a9c-2d4b-4c8e-9f7a-0b5d3e1c8a26", "isActive": true, "type": "reservation" }, "meta": { "requestId": "…" }}- Always send
isActive.truestarts the rule andfalsepauses it, and a rule already in that state is returned unchanged. Omitting it flips the current state, so a retried call would undo the first. - Starting a rule with a blocker returns
400 validation_error. Themessageis the first blocker key anddetails.blockerslists them all. - Pausing never fails on a blocker. It creates nothing new and keeps the tasks already made.
Listing rules
Section titled “Listing rules”GET /task-schedules is paginated (default limit 50, maximum 100) and ordered by name. Each row is the rule in the shape create returns, plus activationBlockers.
| Parameter | Effect |
|---|---|
type | reservation or recurring |
kind | Narrower: reservation, recurring (plain repeats) or vacancy_occupancy. Combines with type |
search | Case-insensitive part of the rule name |
departmentIds | Rules with an item in one of these departments. Repeat the key for each ID |
isActive | true for running rules, false for paused ones |
activationBlockers is filled in only for running rules: a running rule with a blocker has a setup problem, and depending on the blocker it may be creating some, all or none of its tasks. On a paused rule it is always [], even when the rule has blockers; use the preview for those.
Updating a rule
Section titled “Updating a rule”PATCH /task-schedules/{id} takes a data object. Send only what changes: name, description (null clears it), scopes, autoAssign, the stay conditions, or workflows. Changes apply to the rule’s upcoming tasks at once, even while it runs. To start or pause it, use toggleTaskSchedule.
curl -X PATCH "https://api-us.suiteop.com/api/v1/task-schedules/6e1f3a9c-2d4b-4c8e-9f7a-0b5d3e1c8a26" \ -H "Authorization: Bearer sk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{"data": {"name": "Turnover clean", "stayMinNights": null}}'- Rebuild each workflow in its create shape, not the listed row: only its own kind’s keys, and no
triggerTypeorrank. A key from another kind is refused even at0orfalse. Items take theiridback, which keeps their upcoming tasks. Only a repeat workflow accepts its ownid, and ignores it; anidon a reservation, vacancy or occupancy workflow returns400 validation_error. - A reservation rule accepts reservation workflows only, and a calendar rule only calendar workflows. Stay conditions on a calendar rule return
400 validation_error. - To clear the channel condition, send
sourceRuleType: nullandbookingChannels: nulltogether. - An update that sends
workflows, changes the stay conditions, or adds or drops a property inscopescan return409 conflict_errorwith the messagetask.scheduler.concurrent_updatewhen the rule’s tasks are being processed at that moment. Read the rule again and resend. Two saves of the same rule run one after the other.
Pitfalls
Section titled “Pitfalls”- Raw message keys. Task schedule refusals carry message keys rather than sentences. Input checks put them in
details.issues[].message, for examplevalidation.template_or_name_requiredon an item with neither a template nor a name, orvalidation.exactly_one_scope_valueon a scope with the wrong ID. Refusals from the rule’s state, such as a start blocked by a blocker, and checks run against the stored rule on update, such asvalidation.recurrence_overlap, put the key inerror.message. Match on the keys (see Errors). - Property-limited callers. An OAuth token carries its member’s property access. The list shows only rules that reach one of the member’s properties. Toggling a rule needs access to every property it covers, and so does an update that changes the stay conditions or sends
workflows. Ascopesupdate only changes the scope rows the member can write; the others are kept. - Idempotency. Send an
Idempotency-Keyon both create operations so a retry doesn’t make a second rule (see Idempotency). - No delete. A rule you no longer need is paused with
toggleTaskScheduleandisActive: false; delete it in the app if you must.