BlueSuite Developers
REST API v1

Jobs, schedules, and expenses

Jobs, schedules, and expenses in the BlueSuite REST API

Jobs group customer work, visits, and billing. See Events for visits and Payments for recording money received.

Status and holds

Scheduled and in-progress states follow appointments. Manual status updates accept completed, cancelled, or reopen; both the general update and dedicated status endpoint use the guarded status transition. Required forms can prevent completion.

Use POST /jobs/:id/hold for a temporary hold, with an explicit cancel_appointments decision. Resume with POST /jobs/:id/resume, passing the current held_at value as expected_held_at; this prevents acting on a hold that has changed. A recurring job may need a resume_on date. Holds are separate from ordinary status changes.

Recurring schedules

Creating a job with type: "recurring" alone does not set up its visits. Use POST /jobs/:id/schedule to establish the schedule and billing settings together. Dates and times use the workspace timezone. Both times are required unless all_day: true. For custom weekly/monthly schedules, provide repeat_frequency_schedule.days; only send that schedule with repeat_frequency_unit: "custom". A finite duration requires repeat_duration.

billing_type defaults to per_visit, which generates invoices as visits are completed. Use manual when billing will be handled separately. invoice_autopay is a job field; the schedule endpoint takes payment_terms, while ordinary job create/update takes invoice_payment_terms.

To stop repeating work, use POST /jobs/:id/schedule/end with the last date to retain. It removes later scheduled occurrences while preserving completed/invoiced history. Do not delete the job to stop its schedule.

curl 'https://my.bluesuite.app/api/v1/jobs/42/schedule' \
  -H "Authorization: Bearer $BLUESUITE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"start_date":"2026-11-02","start_time":"09:00","end_time":"10:00","repeat_frequency_unit":"weekly","repeat_duration_unit":"forever","billing_type":"manual"}'

Expenses

Expense endpoints are nested under the parent job. Amounts may be positive, zero, or negative, so credits can be recorded. Use Uploads to attach receipts to the job. Expense creation does not itself upload a file.

List jobs using comma-separated statuses or tag_ids to match any selected tag. See line items for pricing.

GET /api/v1/jobs

GET /api/v1/jobs

Required scopes: jobs:read.

Query parameters

FieldType / accepted valuesRequiredDetails
pageintegerNoPage number, starting at 1. Default: 1.
per_pageintegerNoRecords per page, from 1 to 100. Default: 25. minimum: 1. maximum: 100.
searchstringNoSearch text. maxLength: 100.
sort_bystringNoSort column. See the supported columns below when listed.
sort_direction"asc", "desc"NoSort direction. Default: "desc".
contact_idstringNoFilter by contact ID.
quote_idstringNoFilter by quote ID.
statusstringNoFilter by status.
derived_statestringNoComma-separated: missed, overdue, action_required, needs_invoicing, unscheduled, awaiting_payment. Unscheduled matches draft jobs.
tag_idsstringNoComma-separated tag IDs. Tags must match this resource type.

Response

200 with { "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }.

See response conventions and errors.

POST /api/v1/jobs

POST /api/v1/jobs

Required scopes: jobs:write.

JSON body

FieldType / accepted valuesRequiredDetails
namestring or nullNo
contact_idinteger or nullNo
property_idinteger or nullNo
request_idinteger or nullNo
quote_idinteger or nullNo
salesperson_idstring or nullNo
line_itemsarray of object or objectNomaxItems: 500.
line_items[]._type"lineItem"NoFor lineItem rows. Defaults to lineItem when omitted.
line_items[].namestringFor this variantFor lineItem rows. Item name
line_items[].descriptionstring or nullNoFor lineItem rows. Additional description
line_items[].quantitynumberNoFor lineItem rows. Quantity Default: 1.
line_items[].unitPricenumberFor this variantFor lineItem rows. Unit price
line_items[].unitCostnumberNoFor lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0.
line_items[].taxablebooleanNoFor lineItem rows. Whether the item is taxable
line_items[].optionalbooleanNoFor lineItem rows. Whether the item is optional
line_items[]._type"image"For this variantFor image rows.
line_items[].image_pathstringFor this variantFor image rows. Storage path returned by /uploads minLength: 1.
line_items[].image_idinteger or nullNoFor image rows. Upload link ID returned by /uploads
line_items[].descriptionstring or nullNoFor image rows. Caption shown beneath the image
discountnumberNoDefault: 0.
discount_unit"%", "$"NoDefault: "%".
depositnumberNoDefault: 0.
deposit_unit"%", "$"NoDefault: "%".
tax_ratenumberNoDefault: 0.
tax_rate_idinteger or nullNo
totalnumberNoDefault: 0.
status"draft", "scheduled", "in_progress", "completed", "cancelled"NoDefault: "draft".
type"one-off", "recurring"NoDefault: "one-off".
billing_type"per_visit", "manual"NoDefault: "per_visit".
invoice_autopaybooleanNoDefault: false.
invoice_payment_terms"due_on_receipt", "net_7", "net_14", "net_30", "net_60", "net_90" or nullNo
waive_credit_card_feebooleanNoSkip the workspace's online processing fee for this record. Carries onto invoices created from this job. Default: false.
expected_end_datestring or nullNo
expensesJSON valueNo
custom_fieldsJSON value or nullNo

Unknown body fields are rejected. Conditional requirements are explained above.

Response

201 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

GET /api/v1/jobs/:id

GET /api/v1/jobs/:id

Required scopes: jobs:read.

Path parameters: id. Use positive integer record IDs from this workspace.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

PUT /api/v1/jobs/:id

PUT /api/v1/jobs/:id

Required scopes: jobs:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
namestring or nullNo
contact_idinteger or nullNo
property_idinteger or nullNo
request_idinteger or nullNo
quote_idinteger or nullNo
salesperson_idstring or nullNo
line_itemsarray of object or objectNomaxItems: 500.
line_items[]._type"lineItem"NoFor lineItem rows. Defaults to lineItem when omitted.
line_items[].namestringFor this variantFor lineItem rows. Item name
line_items[].descriptionstring or nullNoFor lineItem rows. Additional description
line_items[].quantitynumberNoFor lineItem rows. Quantity Default: 1.
line_items[].unitPricenumberFor this variantFor lineItem rows. Unit price
line_items[].unitCostnumberNoFor lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0.
line_items[].taxablebooleanNoFor lineItem rows. Whether the item is taxable
line_items[].optionalbooleanNoFor lineItem rows. Whether the item is optional
line_items[]._type"image"For this variantFor image rows.
line_items[].image_pathstringFor this variantFor image rows. Storage path returned by /uploads minLength: 1.
line_items[].image_idinteger or nullNoFor image rows. Upload link ID returned by /uploads
line_items[].descriptionstring or nullNoFor image rows. Caption shown beneath the image
discountnumberNoDefault: 0.
discount_unit"%", "$"NoDefault: "%".
depositnumberNoDefault: 0.
deposit_unit"%", "$"NoDefault: "%".
tax_ratenumberNoDefault: 0.
tax_rate_idinteger or nullNo
totalnumberNoDefault: 0.
type"one-off", "recurring"NoDefault: "one-off".
billing_type"per_visit", "manual"NoDefault: "per_visit".
invoice_autopaybooleanNoDefault: false.
invoice_payment_terms"due_on_receipt", "net_7", "net_14", "net_30", "net_60", "net_90" or nullNo
waive_credit_card_feebooleanNoSkip the workspace's online processing fee for this record. Carries onto invoices created from this job. Default: false.
expected_end_datestring or nullNo
expensesJSON valueNo
custom_fieldsJSON value or nullNo
status"completed", "cancelled", "reopen"No

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

DELETE /api/v1/jobs/:id

DELETE /api/v1/jobs/:id

Required scopes: jobs:write.

Path parameters: id. Use positive integer record IDs from this workspace.

No request body.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

GET /api/v1/jobs/:id/expenses

GET /api/v1/jobs/:id/expenses

Required scopes: jobs:read.

Path parameters: id. Use positive integer record IDs from this workspace.

Query parameters

FieldType / accepted valuesRequiredDetails
pageintegerNoPage number, starting at 1. Default: 1.
per_pageintegerNoRecords per page, from 1 to 100. Default: 25. minimum: 1. maximum: 100.
searchstringNoSearch text. maxLength: 100.
sort_bystringNoSort column. See the supported columns below when listed.
sort_direction"asc", "desc"NoSort direction. Default: "desc".

Response

200 with { "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }.

See response conventions and errors.

POST /api/v1/jobs/:id/expenses

POST /api/v1/jobs/:id/expenses

Required scopes: jobs:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
namestringYesminLength: 1.
descriptionstring or nullNo
datestring or nullNo
totalnumberYes
attachmentstring or nullNo
attachmentsarray of stringNo

Unknown body fields are rejected. Conditional requirements are explained above.

Response

201 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

POST /api/v1/jobs/:id/hold

POST /api/v1/jobs/:id/hold

Required scopes: jobs:write, events:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
reason"customer", "materials", "approval", "weather", "other"Yes
notesstringNomaxLength: 2000.
follow_up_onstringNoFormat: date.
cancel_appointmentsbooleanYes

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

POST /api/v1/jobs/:id/resume

POST /api/v1/jobs/:id/resume

Required scopes: jobs:write, events:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
expected_held_atstringYesFormat: date-time.
resume_onstringNoFormat: date.

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

POST /api/v1/jobs/:id/schedule

POST /api/v1/jobs/:id/schedule

Required scopes: jobs:write, events:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
start_datestringYesFirst appointment date, YYYY-MM-DD.
end_datestring or nullNo
start_timestring or nullNo
end_timestring or nullNo
all_daybooleanNoDefault: false.
multi_daybooleanNoDefault: false.
is_flexiblebooleanNoDefault: false.
duration_minutesinteger or nullNo
assignee_idsarray of stringNoDefault: [].
billing_type"per_visit", "manual"NoDefault: "per_visit".
payment_terms"due_on_receipt", "net_7", "net_14", "net_30", "net_60", "net_90"NoDefault: "due_on_receipt".
repeat_frequencyintegerNoDefault: 1.
repeat_frequency_unit"daily", "weekly", "monthly", "yearly", "custom", "forever", "occurrences"NoDefault: "weekly".
repeat_frequency_unit_custom"daily", "weekly", "monthly", "yearly", "custom", "forever", "occurrences"NoDefault: "weekly".
repeat_frequency_scheduleobject or nullNo
repeat_frequency_schedule.daysarray of integerWhen parent supplied
repeat_frequency_schedule.type"weekDays", "monthWeekDays", "monthDays"When parent supplied
repeat_durationinteger or nullNo
repeat_duration_unit"daily", "weekly", "monthly", "yearly", "custom", "forever", "occurrences"NoDefault: "forever".

Unknown body fields are rejected. Conditional requirements are explained above.

Response

201 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

PUT /api/v1/jobs/:id/status

PUT /api/v1/jobs/:id/status

Required scopes: jobs:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
status"completed", "cancelled", "reopen"Yes

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

GET /api/v1/jobs/:id/expenses/:expenseId

GET /api/v1/jobs/:id/expenses/:expenseId

Required scopes: jobs:read.

Path parameters: id, expenseId. Use positive integer record IDs from this workspace.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

PUT /api/v1/jobs/:id/expenses/:expenseId

PUT /api/v1/jobs/:id/expenses/:expenseId

Required scopes: jobs:write.

Path parameters: id, expenseId. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
namestringNominLength: 1.
descriptionstring or nullNo
datestring or nullNo
totalnumberNo
attachmentstring or nullNo
attachmentsarray of stringNo

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

DELETE /api/v1/jobs/:id/expenses/:expenseId

DELETE /api/v1/jobs/:id/expenses/:expenseId

Required scopes: jobs:write.

Path parameters: id, expenseId. Use positive integer record IDs from this workspace.

No request body.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

POST /api/v1/jobs/:id/schedule/end

POST /api/v1/jobs/:id/schedule/end

Required scopes: jobs:write, events:write.

Path parameters: id. Use positive integer record IDs from this workspace.

JSON body

FieldType / accepted valuesRequiredDetails
end_datestringYesLast date the schedule should run. Visits after this are removed. Pattern: ^\d{4}-\d{2}-\d{2}$.

Unknown body fields are rejected. Conditional requirements are explained above.

Response

200 with { "success": true, "data": ... }. The data contains the result of the operation.

See response conventions and errors.

On this page