View Categories

Launching soon on the Shopify App StoreJoin the waitlist

REST API reference: Staff & resources

18 min read

The REST API v1 of Rentals & Bookings for staff & resources: your staff members and their skills, hours and time off; resources such as boats, vehicles and rooms; the jobs they are given (bookings that need staff, departures, maintenance tickets and your own custom jobs); assignments, who is free, conflicts and the scheduled hours for payroll. It is part of the core REST reference, with REST API reference: bookings and availability and REST API reference: warehouse, stock and settings. Keys, limits, pagination and the error format are in the API overview. The same operations are in GraphQL: GraphQL API reference: Staff & resources.

Every endpoint on this page needs the Rentals & Bookings app. GET needs a read key; everything else needs a key with Allow changes (write): a read key gets 403 rentals_rest_forbidden. On the Free plan the API reads only: a change answers 403 rentals_rest_plan_required with data.plan: "free" and data.required_plan: "regular". The examples use two shell variables:

API="https://YOUR-API-ADDRESS/api/v1"   # the REST address from Settings › API
KEY="sirb_your_secret_key"

Ids in the examples are made up: staff member 7, resource 3, booking 5471, location 801, product 88001, assignment 120.

How it works #

  • Times are the store’s wall clock, Y-m-d H:i (2026-12-05 09:00). A date alone is that day: as a start it means 00:00, as an end it runs to the end of the day. Timestamps (created_at, updated_at) are ISO 8601.
  • Ids are numbers. A skill is named by its code (guide). A job is named by its type and id: /staff/jobs/booking/5471.
  • Request bodies are JSON with snake_case names; nested names too (notify.day_sheet, skills[].expires_on, stock_link.product_id). On a change only the fields you send change.
  • Answers leave out fields that are empty (null).
  • Lists of staff, resources, jobs and assignments are paged with page and per_page (default 20, at most 200), with the totals in the X-Total and X-Total-Pages headers. The other lists come whole.
  • Ranges: lists of jobs, assignments, unassigned jobs and conflicts, and a schedule, cover from to to; without them, today and the next 13 days. At most 92 days.
  • Jobs never carry a customer’s name or email: only the order name and ids.

Job types #

typeThe jobIts id
bookingA booking of a product whose staff & resources settings ask for staff or resources. It follows the booking: it moves with its dates and is cancelled with it.The booking id (5471).
departureA departure (a scheduled run of a tour or activity). See REST API reference: Departures.The departure id.
maintenance_ticketA maintenance ticket.The ticket id.
customA job you make in the app or through this API: cleaning the boats, a staff meeting.A 26-character id (01jd8x3k4m5n6p7q8r9s0t1v2w).

Only custom jobs are made, changed and cancelled here; the others follow their booking, departure or ticket, and only their assignments change. A job that runs over several days with its own hours each day is one job per day: one keeps the owner’s id, the others add the day and start time (5471-20261206-0900).

Staff members #

FieldNotes
idThe staff member’s number.
name, email, phonephone in international format (+16145550123).
statusactive, inactive (kept, but given no jobs) or archived (removed).
colourTheir colour on the schedule, #RRGGBB.
home_location_id, location_idsTheir home location and the others they also work at (Shopify location ids). Neither: they work anywhere.
hours_modeopening (they work your opening hours) or own (hours).
hoursTheir own weekly hours (one staff member only): [{weekday, from, to, location_id}], weekday 1 = Monday … 7 = Sunday, HH:MM; a to at or before from runs past midnight.
daily_max_minutesMost minutes a day: a soft limit, a warning on the schedule, never a refusal.
notify{email, sms, day_sheet}: tell them by email and by text message about new jobs, changed jobs and jobs they are taken off; day_sheet emails them their day’s jobs at 7:00 (with email on).
notes
skills[{code, level, expires_on, expired}]: level 1-3 or null for any; expires_on (Y-m-d) for a licence or certificate.
staff_user_id, limitedThe Shopify staff member who uses the app as them; limited: that login sees only its own schedule.
pos_staff_member_idTheir Shopify POS staff member.
time_offTime off and extra shifts from today on (one staff member only): [{id, kind, start, end, reason, source}], kind time_off or extra.
created_at, updated_at

GET /api/v1/staff #

Staff members by name. Paged.

ParameterNotes
statusactive (default), inactive, archived or all.
searchPart of a name, email or phone number.
skillA skill code: only the people who hold it.
location_idPeople who work at that location (home or other), or anywhere.
page, per_page
curl -s "$API/staff?skill=guide" -H "Authorization: Bearer $KEY"
# 200, X-Total: 1
[{"id": 7, "name": "Alex Rivera", "email": "[email protected]", "phone": "+16145550123", "status": "active",
  "colour": "#16304F", "home_location_id": 801, "location_ids": [], "hours_mode": "own",
  "notify": {"email": true, "sms": false, "day_sheet": true},
  "skills": [{"code": "guide", "level": 2, "expires_on": null, "expired": false},
             {"code": "captain", "level": null, "expires_on": "2027-03-31", "expired": false}],
  "limited": false, "created_at": "2026-11-02T14:05:11+00:00", "updated_at": "2026-11-09T08:30:40+00:00"}]

POST /api/v1/staff #

Add a staff member. Answers 201 with the staff member. A name and an email or a phone number are needed.

FieldNotes
nameRequired, up to 120 characters.
email, phoneOne of them is required.
statusactive (default) or inactive.
colour#RRGGBB; without it the next colour of the palette.
home_location_id, location_idsShopify location ids of your rental locations.
hours_modeopening (default) or own. Sending hours sets own; opening clears the hours.
hours[{weekday, from, to, location_id?}], at most 21 rows; replaces the list.
daily_max_minutes1-1440, or null for none.
notify{email?, sms?, day_sheet?}; defaults email on, text and day sheet off.
notesUp to 5000 characters.
skills[{code, level?, expires_on?}]; replaces the list. A skill that expires needs expires_on.
staff_user_id, pos_staff_member_idShopify staff member ids. One login links to one staff member: another gets 409 rentals_rest_duplicate.
curl -s -X POST "$API/staff" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name": "Sam Patel", "email": "[email protected]", "home_location_id": 801,
       "hours": [{"weekday": 6, "from": "08:00", "to": "16:00"}, {"weekday": 7, "from": "08:00", "to": "16:00"}],
       "skills": [{"code": "guide"}, {"code": "captain", "expires_on": "2027-06-30"}]}'
# 201: the staff member, with "hours_mode": "own", "hours" and "time_off": []

GET /api/v1/staff/{id} #

One staff member with hours and time_off. 404 rentals_rest_not_found for an unknown id.

PUT or PATCH /api/v1/staff/{id} #

Change a staff member: the same fields as POST, only the ones you send change; skills and hours replace their lists. Setting status to inactive cancels their assignments that haven’t ended; setting active brings an archived person back. Answers the staff member.

DELETE /api/v1/staff/{id} #

Remove (archive) a staff member: they become archived, and their assignments that haven’t ended are cancelled. Past assignments stay. Answers the staff member.

POST /api/v1/staff/{id}/time-off #

Add time off, or an extra shift outside their hours. Answers 201 with the staff member.

FieldNotes
startRequired: Y-m-d or Y-m-d H:i.
endAfter start. A date alone runs to the end of that day; leave it out for a whole day when start is a date alone.
kindtime_off (default) or extra.
reasonUp to 200 characters.
curl -s -X POST "$API/staff/7/time-off" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"start": "2026-12-24", "end": "2026-12-26", "reason": "Holiday"}'
# 201: the staff member, "time_off": [{"id": 31, "kind": "time_off", "start": "2026-12-24 00:00", "end": "2026-12-27 00:00",
#                                      "reason": "Holiday", "source": "manual"}]

DELETE /api/v1/staff/{id}/time-off/{block_id} #

Delete a time off or extra shift. Answers the staff member.

GET /api/v1/staff/{id}/schedule #

Their assignments (each with its job) and their time off between from and to (default: today and the next 13 days): {from, to, assignments, blocks}.

curl -s "$API/staff/7/schedule?from=2026-12-05&to=2026-12-06" -H "Authorization: Bearer $KEY"
{"from": "2026-12-05 00:00", "to": "2026-12-07 00:00",
 "assignments": [{"id": 120, "job_type": "booking", "job_id": "5471", "subject_type": "staff", "subject_id": 7, "role": "guide",
                  "start": "2026-12-05 08:30", "end": "2026-12-05 12:15", "location_id": 801, "status": "confirmed",
                  "source": "manual", "warnings": [], "created_at": "2026-11-20T10:02:13+00:00", "updated_at": "2026-11-20T10:02:13+00:00",
                  "job": {"type": "booking", "id": "5471", "ref": "booking:5471", "title": "Sunset kayak tour", "url": "/bookings/5471",
                          "order_name": "#1042", "start": "2026-12-05 09:00", "end": "2026-12-05 12:00", "location_id": 801,
                          "status": "open", "guests": 6, "units": 6, "before": 30, "after": 15, "needs": [ … ]}}],
 "blocks": []}

GET /api/v1/staff/free #

Staff free for a window, best first: already working another part of the same job that day, then at their home location, then the fewest minutes booked that day, then the fewest other skills. Each is a candidate.

ParameterNotes
start, endThe window.
job_type, job_idOr a job: its window; its own assignments don’t count as busy, and the people already on it are left out.
skill, levelA skill code and a level 1-3.
location_idA location (default: the job’s).
include_busytrue: also list the busy ones, with their conflicts.
limit1-100, default 20.
curl -s "$API/staff/free?job_type=booking&job_id=5471&skill=guide" -H "Authorization: Bearer $KEY"
[{"subject_type": "staff", "id": 7, "name": "Alex Rivera", "colour": "#16304F", "rank": 1, "qualified": true, "hard": false, "conflicts": []}]

Skills #

A skill is {code, label, expires, archived, holders}, listed in the store’s order: expires marks a licence or certificate (each holder has a valid-until date), holders counts the active staff who hold it. A store starts with guide, driver, technician, captain (expires) and instructor; at most 100.

GET /api/v1/staff/skills #

The skills. archived=1 adds the archived ones.

curl -s "$API/staff/skills" -H "Authorization: Bearer $KEY"
[{"code": "guide", "label": "Guide", "expires": false, "archived": false, "holders": 4},
 {"code": "captain", "label": "Captain", "expires": true, "archived": false, "holders": 2}]

POST /api/v1/staff/skills #

Add a skill: {label, expires?}. The code is made from the label (Lead guide → lead-guide). Answers 201 with the skill. An active skill with that name is 409 rentals_rest_duplicate; an archived one comes back.

PUT or PATCH /api/v1/staff/skills/{code} #

Change a skill’s label or expires. Answers the skill. An unknown code with a label adds the skill with that code.

DELETE /api/v1/staff/skills/{code} #

Archive a skill: the staff who hold it keep it, and needs that ask for it still do, but it is no longer offered. Answers the skill.

Resources #

A resource is something a job needs besides people: a boat, a van, a room, equipment.

FieldNotes
idThe resource’s number.
name
typeboat, vehicle, room, equipment or your own word (stored lower case with dashes).
seatsHow many people it takes (0 = not counted).
location_idIts location; none = any.
statusactive, inactive or archived.
colour#RRGGBB.
stock_link{product_id, variant_id, units}: the rental stock it stands for. It can’t be assigned when those units aren’t free (stock_unavailable).
notes
hoursWeekly hours (one resource only), as for staff.
blocksBlocked times from today on (one resource only): [{id, kind: "blocked", start, end, reason, source}].
created_at, updated_at

GET /api/v1/staff/resources #

Resources by name. Paged. Parameters: status (active default, inactive, archived, all), search (part of the name), type, location_id (at that location or none), page, per_page.

curl -s "$API/staff/resources?type=boat" -H "Authorization: Bearer $KEY"
# 200, X-Total: 1
[{"id": 3, "name": "Pontoon 2", "type": "boat", "seats": 12, "location_id": 801, "status": "active", "colour": "#F85112",
  "created_at": "2026-11-02T14:10:00+00:00", "updated_at": "2026-11-02T14:10:00+00:00"}]

POST /api/v1/staff/resources #

Add a resource. Answers 201 with the resource.

FieldNotes
nameRequired, up to 120 characters.
typeRequired.
seats0-10000.
location_idOne of your rental locations.
statusactive (default) or inactive.
colour, notes
stock_link{product_id, variant_id?, units?}: a rental product of your store, units 1-1000 (default 1); null removes the link.
hours[{weekday, from, to, location_id?}]; replaces the list.
curl -s -X POST "$API/staff/resources" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name": "Pontoon 3", "type": "boat", "seats": 12, "location_id": 801, "stock_link": {"product_id": 88001, "units": 1}}'
# 201: the resource, with "hours": [] and "blocks": []

GET /api/v1/staff/resources/{id} #

One resource with hours and blocks.

PUT or PATCH /api/v1/staff/resources/{id} #

Change a resource: the same fields as POST, only the ones you send change. Setting inactive cancels its assignments that haven’t ended. Answers the resource.

DELETE /api/v1/staff/resources/{id} #

Remove (archive) a resource; its assignments that haven’t ended are cancelled. Answers the resource.

POST /api/v1/staff/resources/{id}/blocks #

Block a resource for a time: {start, end?, reason?} (as for time off). Answers 201 with the resource.

DELETE /api/v1/staff/resources/{id}/blocks/{block_id} #

Delete a blocked time. Answers the resource.

GET /api/v1/staff/resources/{id}/schedule #

The resource’s assignments and blocked times between from and to, as for a staff member.

GET /api/v1/staff/resources/free #

Resources free for a window, best first (the smallest that seats enough comes first). The same parameters as GET /api/v1/staff/free, with type and min_seats instead of skill and level.

Jobs #

FieldNotes
type, id, refThe job type, its id and both together (booking:5471).
titleThe product, the departure, the ticket or your title.
urlThe job’s page in the app (a path).
order_nameA booking’s order.
colour, notesA custom job’s.
start, endThe job’s window.
location_id
statusopen or cancelled; cancel_reason.
guests, unitsThe party and the units booked.
before, afterMinutes before and after the job that its assignments also take (set-up, travel, clean-up).
auto_assignFrom the product’s settings: off, suggest or auto.
needsWhat the job needs, one row per requirement: {kind, skill, level, type, count, per_guests, per_units, seats, seats_total, mode, role, assigned, missing, reason}. count is how many this job needs (a ratio row works it out from the guests or units), role the slot assignments fill, assigned the staff or resource ids that fill it, missing how many are still needed, reason unfilled or too_small when something is missing. mode limit means the product refuses a sale nobody is free for.
missingHow many are still missing over all needs (open jobs).
assignmentsOne job only: its assignments.
updated_at

GET /api/v1/staff/jobs #

Jobs between from and to, soonest first. Paged. type (a job type), status (open default, cancelled, all), location_id.

curl -s "$API/staff/jobs?from=2026-12-05&to=2026-12-05&type=booking" -H "Authorization: Bearer $KEY"
# 200, X-Total: 1
[{"type": "booking", "id": "5471", "ref": "booking:5471", "title": "Sunset kayak tour", "url": "/bookings/5471", "order_name": "#1042",
  "start": "2026-12-05 09:00", "end": "2026-12-05 12:00", "location_id": 801, "status": "open", "guests": 6, "units": 6,
  "before": 30, "after": 15, "auto_assign": "suggest",
  "needs": [{"kind": "staff", "skill": "guide", "per_guests": 6, "mode": "track", "role": "guide", "count": 1, "assigned": [7], "missing": 0},
            {"kind": "resource", "type": "boat", "count": 1, "seats": "party", "mode": "track", "role": "boat", "assigned": [], "missing": 1, "reason": "unfilled"}],
  "missing": 1, "updated_at": "2026-11-20T10:02:13+00:00"}]

POST /api/v1/staff/jobs #

Make a custom job. Answers 201 with the job.

FieldNotes
titleRequired, up to 200 characters.
start, endRequired.
location_idOne of your rental locations.
needsAt most 10 rows: {kind: staff|resource, skill?, level?, type?, count? (1-100, default 1) | per_guests? | per_units?, seats? (a number or "party"), seats_total?, role?}. A staff row without skill takes anyone, a resource row without type any resource; seats_total: true means the resources of the row seat the party together.
notesUp to 5000 characters.
colour#RRGGBB.
before, afterMinutes, or 30m, 1h, 1h30m (0-1440).
curl -s -X POST "$API/staff/jobs" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"title": "Clean the boats", "start": "2026-12-05 07:00", "end": "2026-12-05 08:30", "location_id": 801,
       "needs": [{"kind": "staff", "count": 2}]}'
# 201: {"type": "custom", "id": "01jd8x3k4m5n6p7q8r9s0t1v2w", "ref": "custom:01jd8x3k4m5n6p7q8r9s0t1v2w", "title": "Clean the boats",
#       "start": "2026-12-05 07:00", "end": "2026-12-05 08:30", "status": "open", "needs": [{"kind": "staff", "count": 2, "mode": "track",
#       "role": "staff", "assigned": [], "missing": 2, "reason": "unfilled"}], "missing": 2, "assignments": [], …}

GET /api/v1/staff/jobs/{type}/{id} #

One job with its needs and assignments.

PUT or PATCH /api/v1/staff/jobs/custom/{id} #

Change a custom job: the same fields as POST, only the ones you send change; its assignments move with it. 409 rentals_rest_job_cancelled for a cancelled job.

POST /api/v1/staff/jobs/{type}/{id}/cancel #

Cancel a custom job and its assignments: {reason?}. Answers the job. Other job types follow their booking, departure or ticket: 409 rentals_rest_not_editable.

Assignments #

FieldNotes
idThe assignment’s number.
job_type, job_idThe job.
subject_type, subject_idstaff or resource, and its id.
roleThe slot it fills (a skill, a type or your own word).
start, endThe job’s window with its before and after, or a window of its own.
location_id
statusplanned (waiting for the staff member to accept), confirmed, declined, done or cancelled.
sourcemanual, suggested or auto.
warningsThe conflict codes it had when it was made or last moved.
overrideSet when it was assigned through a hard conflict: {reason, by, at, codes}.
notes, cancel_reason
created_at, updated_at

Candidates and conflicts #

A candidate (who is free) is {subject_type, id, name, colour, rank, qualified, hard, conflicts, next_free_at, explain}: qualified means no conflict at all (it is what suggestions and automatic assignment use), hard that at least one conflict is hard, next_free_at when a busy one is next free (looking 14 days ahead), explain why it ranks where it does: {continuity, home, minutes, other_skills} for staff (seats instead of other_skills for resources), where continuity means already on another part of the same job that day, home at their home location and minutes the minutes already booked that day.

A conflict is {code, hard, message, subject_type, subject_id, assignment_id, job, other_job, start, end}, job and other_job as type:id. Hard conflicts refuse an assignment unless you force it with a reason; soft ones are kept as warnings.

codeHardMeaning
overlapyesAlready on another job then.
time_offyesHas time off then.
blockedyesThe resource is blocked then.
inactiveyesInactive or archived.
stock_unavailableyesThe resource’s linked stock is not free then.
invalid_jobyesThe job’s times can’t be read.
outside_hoursnoOutside their working hours.
missing_skillnoDoesn’t have the skill.
skill_expirednoThe skill has expired.
wrong_locationnoWorks at another location.
daily_maxnoOver their hours for the day.
too_smallnoNot enough seats.

GET /api/v1/staff/assignments #

Assignments between from and to, or every assignment of one job with job_type and job_id. Paged. staff_id or resource_id; status: live (default: planned, confirmed and done), planned, confirmed, declined, done, cancelled or all.

GET /api/v1/staff/jobs/{type}/{id}/assignments #

A job’s assignments (at most 200), with the same status filter.

POST /api/v1/staff/jobs/{type}/{id}/assignments #

Give a staff member or resource the job. Answers 201 with the assignment; soft conflicts are in its warnings.

FieldNotes
subject_typestaff (default) or resource.
subject_idRequired.
roleThe slot (default: the need it fills).
needThe need it fills (its index in needs); default: the best match.
start, endA window of its own (default: the job’s, with its before and after).
force{reason}: assign through a hard conflict. Without reason: 400 rentals_rest_invalid_param (data.field: force.reason).
statusplanned or confirmed. Default: planned when Settings › Staff › Staff confirm their jobs is on, confirmed otherwise; resources start confirmed.
notesUp to 2000 characters.
curl -s -X POST "$API/staff/jobs/booking/5471/assignments" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"subject_type": "staff", "subject_id": 7}'
# 409 when Alex is already on another job then:
{"code": "rentals_rest_conflict",
 "message": "Can't assign: Already on another job then (booking:5480, 2026-12-05 08:00 – 2026-12-05 11:00). Assign anyway with a reason.",
 "data": {"field": "subjectId", "conflicts": [{"code": "overlap", "hard": true,
          "message": "Already on another job then (booking:5480, 2026-12-05 08:00 – 2026-12-05 11:00)",
          "subjectType": "staff", "subjectId": 7, "job": "booking:5471", "otherJob": "booking:5480",
          "start": "2026-12-05 08:00", "end": "2026-12-05 11:00"}], "status": 409}}

# the same with a reason: 201, "override": {"reason": "Swaps with Sam at 09:00", "by": "api:sirb_3f9a2c1d", "at": "…", "codes": ["overlap"]}
curl -s -X POST "$API/staff/jobs/booking/5471/assignments" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"subject_id": 7, "force": {"reason": "Swaps with Sam at 09:00"}}'

In this answer the conflicts keep the camelCase names of the error data (subjectType, otherJob, assignmentId); the lists of conflicts and candidates use snake_case. The same staff member or resource twice on one job is 409 rentals_rest_already_assigned; a cancelled job is 409 rentals_rest_job_cancelled.

POST /api/v1/staff/jobs/{type}/{id}/auto-assign #

Fill the job’s missing needs: {mode: suggest|commit}. suggest (the default) writes nothing; commit assigns the suggestion (source: "auto"), skipping anyone who conflicts. Answers {assigned, suggested, unmet, job}: suggested per need {index, kind, role, count, missing, matched, candidates} (matched = the ids commit would assign), unmet per need still short {index, role, missing, reason}.

DELETE /api/v1/staff/assignments/{id} #

Cancel an assignment; ?reason= is optional. Answers the assignment. One already cancelled or declined comes back unchanged.

POST /api/v1/staff/assignments/{id}/reassign #

Give the assignment’s slot (role and window) to someone else in one step: {subject_type?, subject_id, force?: {reason}, status?, notes?}. The old assignment is cancelled; the new one is checked like a new assignment and answered. 409 rentals_rest_not_live when the assignment is no longer active.

Schedule, conflicts and hours #

GET /api/v1/staff/unassigned #

Open jobs still missing staff or resources between from and to (at most 200), soonest first, with their needs. location_id narrows it.

GET /api/v1/staff/conflicts #

Every conflict of the assignments between from and to. location_id narrows it.

GET /api/v1/staff/hours #

Scheduled hours per staff member per day, for payroll: their planned, confirmed and done assignments, overlapping ones counted once, a night job split at midnight.

ParameterNotes
from, toDays (Y-m-d), to included. Default 7 days from today; at most 92 days (400 rentals_rest_invalid_param).
location_id, staff_idNarrow it.
curl -s "$API/staff/hours?from=2026-12-01&to=2026-12-07" -H "Authorization: Bearer $KEY"
{"from": "2026-12-01", "to": "2026-12-07",
 "rows": [{"staff_id": 7, "name": "Alex Rivera", "email": "[email protected]", "date": "2026-12-05", "minutes": 225, "hours": 3.75,
           "jobs": 1, "checked_in": 1}],
 "totals": [{"staff_id": 7, "name": "Alex Rivera", "minutes": 225, "hours": 3.75, "jobs": 1, "checked_in": 1, "days": 1}]}

GET /api/v1/staff/hours.csv #

The same rows as a CSV file, one row per person per day, with the same parameters. Columns: Date, Staff member, Email, Hours, Minutes, Jobs, Checked in.

Errors #

The error format is in the API overview: {code, message, data: {status, field?, …}}, data.field naming the field.

CodeStatusWhen
rentals_rest_invalid_param400A field is missing or wrong: a name, an email or phone (one of them is needed), a colour, a location that isn’t one of yours, a time, a window whose end isn’t after its start, a range over 92 days, a skill code the store doesn’t have, a skill that expires without expires_on, a need row, force without a reason.
rentals_rest_too_many400The store has the most staff members (500), resources (500) or skills (100).
rentals_rest_not_found404The staff member, resource, skill, time off or blocked time, job, job type, need (need index) or assignment doesn’t exist.
rentals_rest_duplicate409An active skill with that name, or a Shopify login already linked to another staff member.
rentals_rest_conflict409A hard conflict; the message names the first, data.conflicts lists every conflict, hard and soft. Send force.reason to assign anyway.
rentals_rest_already_assigned409Already on this job.
rentals_rest_job_cancelled409The job is cancelled.
rentals_rest_not_editable409Only custom jobs are changed or cancelled here.
rentals_rest_not_live409The assignment is no longer active.
rentals_rest_forbidden403A read key on a change.
rentals_rest_plan_required403A change on the Free plan.