Jobs
Create, schedule, and update jobs using the Zoop MCP tool catalog.
A job is the core unit of work in Zoop — a visit, task, or project you perform for a customer. This page covers the five API tools that let you list, fetch, create, update, and cancel jobs.
Jobs move through four statuses: unscheduled → scheduled → done (or cancelled at any point). See the status machine section for the full rules.
Required scopes
Every API call in Zoop needs a scope — a permission that says what the token is allowed to do. You set scopes when you create an API key or start an OAuth flow.
| Operation | Scope needed |
|---|---|
| Read jobs | read:jobs |
| Create, update, or cancel jobs | write:jobs |
write:jobs does not imply read:jobs. Grant both if your integration needs to read and write jobs. See Scopes for the full scope catalog.
User actor requirement
Some tools require a user actor — a token that is tied to a specific Zoop user (either an OAuth access token or a user-bound API key). This is different from a tenant key (prefix zoop_tk_), which belongs to the account as a whole and has no user identity attached.
jobs.create, jobs.update, and jobs.cancel require a user actor. If you call them with a tenant key, you will get an invalid_input error. See API keys to understand the difference and pick the right token type.
Tools
jobs.list
Returns one page of jobs for the tenant. Results are sorted by scheduled_start ascending (jobs with no start time come last), then by created_at descending. Each page holds up to 20 jobs.
Scope: read:jobs
Filter by status. One of unscheduled, scheduled, done, or cancelled.
Return only jobs for this customer.
Return only jobs that belong to this recurring series.
Return jobs whose scheduled_start is at or after this timestamp.
Return jobs whose scheduled_start is at or before this timestamp.
Case-insensitive substring match against the job title.
1-based page number.
Response fields:
Array of job summary objects. Each row includes id, title, status, scheduled_start, scheduled_end, customer_id, series_id, flat_price, a nested customer object (id, display_name), and a nested assignments array (user_id).
Total number of jobs matching the filters (across all pages).
The current page number.
The page size (always 20).
jobs.get
Fetches the full record for a single job by its ID. The response includes line items, team assignments, and up to 50 media previews (photos and videos attached to the job).
Scope: read:jobs
The job ID.
Returns a not_found error if the job ID does not exist in this tenant.
Response fields:
unscheduled, scheduled, done, cancelled.status is set to done.Line items. Each has id, catalog_item_id (UUID or null), description, unit_price, quantity, total, and sort_order.
Users assigned to the job. Each has user_id, assigned_by, and assigned_at.
Up to 50 media previews. Each has id, job_id, storage_path, kind (photo or video), filename, size_bytes, caption, uploaded_by, and uploaded_at.
Thin customer summary: id and display_name.
jobs.create
Creates a new job. Requires a user actor (see user actor requirement above).
Scope: write:jobs
Job title. 1–200 characters.
Link the job to a customer. Omit or pass null to leave it unlinked.
Optional notes or work description. Max 5,000 characters.
Optional flat price override. Must be zero or greater. When set, this replaces the sum of line items for billing.
When the job starts. Providing this automatically sets status to scheduled. Omitting it leaves the job unscheduled. Use ISO 8601 format, for example 2026-06-16T09:00:00Z.
Optional end time. Has no effect on status.
Line items to attach. Each object must include:
description(string, 1–500 characters)unit_price(number, non-negative)quantity(number, positive)catalog_item_id(UUID, optional — link to a pricebook item)sort_order(integer, optional — defaults to array index)
Team members to assign to this job. Every ID must belong to your tenant — if even one ID is invalid, the entire request fails with invalid_input.
Returns the newly created job row. The shape matches jobs.get, except the nested job_line_items, job_assignments, and job_media arrays are not included.
jobs.update
Partially updates a job. Only the fields you send are changed — you do not need to resend the whole record. Requires a user actor (see user actor requirement above).
Scope: write:jobs
The job to update.
New title. 1–200 characters.
Change or unlink the customer.
Updated description. Max 5,000 characters.
Updated flat price. Pass null to clear it.
Explicit status change. One of unscheduled, scheduled, done, or cancelled. Setting done stamps completed_at with the current time. Setting any other status clears completed_at.
Update or clear the start time. You do not need to set status separately — Zoop derives it for you: an unscheduled job that gets a start time becomes scheduled; a scheduled job whose start time is cleared becomes unscheduled. Jobs already in done or cancelled are not affected by this auto-derive.
Update or clear the end time.
When supplied, replaces the entire line-item list. Same shape as jobs.create. Omit this field to leave existing line items untouched — there is no way to edit a single line item in isolation.
When supplied, replaces the entire assignment list. Omit this field to leave existing assignments untouched.
Returns the updated job row.
jobs.cancel
Cancels a job by setting its status to cancelled. The job record is preserved in full — history, line items, and assignments all stay intact. Requires a user actor (see user actor requirement above).
Scope: write:jobs
The job to cancel.
Cancellation is not permanent — but it is one-way via this tool. To reopen a cancelled job, call jobs.update with status="unscheduled" or status="scheduled".
Returns the updated job row with status: "cancelled".
Example: create a scheduled job
This example creates a job for a customer and assigns it to a team member. Replace the UUIDs with real IDs from your tenant — you can get a customer ID from customers.list and a user ID from your Zoop account settings.
curl -X POST https://app.zoop.example/api/mcp \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "jobs.create",
"arguments": {
"title": "Replace kitchen faucet",
"customer_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"description": "Customer reported dripping cold side. Bring 1/2-inch compression fittings.",
"scheduled_start": "2026-06-16T09:00:00Z",
"scheduled_end": "2026-06-16T11:00:00Z",
"assigned_user_ids": ["f9e8d7c6-b5a4-3210-fedc-ba9876543210"],
"line_items": [
{
"description": "Labor — faucet replacement",
"unit_price": 95.00,
"quantity": 2
},
{
"description": "Kitchen faucet (customer-supplied)",
"unit_price": 0,
"quantity": 1
}
]
}
}
}'
const response = await fetch('https://app.zoop.example/api/mcp', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'jobs.create',
arguments: {
title: 'Replace kitchen faucet',
customer_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
description: 'Customer reported dripping cold side. Bring 1/2-inch compression fittings.',
scheduled_start: '2026-06-16T09:00:00Z',
scheduled_end: '2026-06-16T11:00:00Z',
assigned_user_ids: ['f9e8d7c6-b5a4-3210-fedc-ba9876543210'],
line_items: [
{
description: 'Labor — faucet replacement',
unit_price: 95.00,
quantity: 2,
},
{
description: 'Kitchen faucet (customer-supplied)',
unit_price: 0,
quantity: 1,
},
],
},
},
}),
})
const result = await response.json()
// result.result contains the new job row
What happens:
Status is derived automatically
Because scheduled_start is set, Zoop sets status to scheduled. You do not need to pass status explicitly.
Assignments are validated
Each user_id in assigned_user_ids is checked against the tenant's membership list. A single invalid ID fails the entire request with invalid_input.
Job row is returned
The response contains the persisted job row, including the server-assigned id and created_at.
Status machine
Jobs move through four statuses. Zoop sets status for you in many cases — you rarely need to set it explicitly.
| Status | Meaning |
|---|---|
unscheduled | Created without a start time, or the start time was cleared. |
scheduled | Has a scheduled_start. Set automatically when you provide a start time. |
done | Work complete. completed_at is stamped the moment you set this status. |
cancelled | Set via jobs.cancel or by passing status="cancelled" to jobs.update. |
Neither done nor cancelled locks the job permanently. You can reopen a done or cancelled job by patching status back to scheduled or unscheduled.
Error reference
| Error kind | Common cause | What to do |
|---|---|---|
invalid_input | Missing title, a non-UUID value in assigned_user_ids, an assigned user who is not a member of your tenant, or a tenant-key token used on a write tool. | Check the fields in the error message. Confirm user IDs belong to your tenant. If you are using a zoop_tk_ token, switch to a user-bound token. |
not_found | The job ID does not exist in this tenant. | Verify the ID with jobs.list or check that you are using the right tenant's credentials. |
insufficient_scope | The token is missing read:jobs or write:jobs. | Re-issue the token with the required scopes. See Scopes. |
See Errors for the full error shape and HTTP status codes.
Related pages
- Job series — create and manage recurring job templates.
- Invoices — turn a completed job into an invoice.
- Quotes — jobs can be created from approved quotes.
- Catalog items — link line items to your pricebook.
- Scopes — full scope catalog and token requirements.