Crisphive
OfficialCrisphive is an agentic AI scheduling/dispatch backend for field-service businesses — it lets an agent manage customers, book and schedule jobs, route technicians/crews by skills, location and live availability, and handle emergency or sick-call rescheduling through a deterministic solver.
Customers (CRM): full CRUD —
createCustomer,getCustomer,listCustomers(search, filters,sincecursor for CRM sync),updateCustomer(partial updates, clear fields),deleteCustomer(soft).Bookings / work orders: create (
createJobRequestwith dates+periods, priority, skills, SLA), list/get, timeline, change feed (listJobRequestChanges), and booking-window discovery (listJobRequestBookingWindows).Scheduling actions:
quoteJobRequest(duration, crew plan),confirmJobRequest(assign technician, force tech),bookAndConfirmJobRequest(one-call booking), plus move preview/commit.Move a scheduled job:
previewJobRequestMove/commitJobRequestMove— cascade pushes displaced jobs back, supports overtime or next-day modes, drift/feasibility error handling.Emergency (P0) dispatch:
listEmergencyCandidates(ranked by fastest arrival + displacement preview),previewEmergencyReschedule,commitEmergencyReschedule(cascade reschedule or reassign).Absence / sick call:
previewAbsenceResolve/commitAbsenceResolvere-staff a technician's whole day at unchanged times; time off viacreateTechnicianTimeOff,listTechnicianTimeOff,deleteTechnicianTimeOff.Matching & availability reads:
listMatchingSlots,listCrewCandidates,getTechnicianSchedule,listNearbyTechnicians— engine-computed availability, scores, ETAs.Catalog management: job types, skill categories, skills, service areas, vehicles — full CRUD via
createJobType,createSkill,createServiceArea,createVehicle, etc.Team & fleet roster:
listTechnicians,getTechnician,listVehicles,getVehicle, plus HR-style updates (createTechnician,updateTechnician,deleteTechnician) and relation setters for buddies, leads, vehicles, service areas and skills.Priority control:
updateJobPriority(P0–P3, SLA deadline with auto-escalation).Webhooks:
listWebhookEventTypes,createWebhookEndpoint,deleteWebhookEndpointfor REST-hook triggers.Agent-friendliness: 67 tools matching the REST
/v1API, pagination viameta, idempotency keys on create/commit tools, stableerror_codeenvelope, OAuth 2.1 or bearer API key (chsk_live_/chsk_test_).
This server has a remotely hosted endpoint. Open its connector listing for connection details, authentication requirements, and setup instructions:
Crisphive MCP
The official MCP (Model Context Protocol) server for the Crisphive API — agentic AI scheduling infrastructure for field operations.
Lets AI agents — Claude, ChatGPT, Gemini, Cursor or any MCP client — match schedules between customers and businesses and route crews to jobs by location, skills, and real-time availability: job booking & appointment scheduling, work-order tracking, availability from a live dispatch & scheduling engine, customer (CRM) sync, service catalogs, technician & crew rosters, geographic service territories and fleet — for trades and home services such as HVAC, plumbing, electrical, cleaning, appliance repair and property maintenance. Scheduling is constraint-based on a deterministic solver: the agent handles the conversation, the solver makes the decision — same inputs, same plan, never an LLM guessing at a calendar. Hosted remote server; nothing to install or run (this repository holds the documentation and registry manifest).
https://api.crisphive.com/mcpTry these first
Connect (a chsk_test_ sandbox key is enough), then paste any of these straight
into your agent:
Job creation — "Schedule a 2-hour HVAC job at 145 Laurier Ave W tomorrow for Marie Tremblay, 613-555-0142." (
createCustomer → listJobRequestBookingWindows → createJobRequest → quoteJobRequest → confirmJobRequest)Emergency insertion — "Emergency plumbing job now at 99 Bank St for David Okafor (613-555-0198) — show me what gets rescheduled." (
listEmergencyCandidates → previewEmergencyReschedule → commitEmergencyReschedule)Daily outline — "Outline my day tomorrow and flag anything at risk." (
listJobRequests → getTechnicianSchedule) . Sick call — "Dmitri called in sick for tomorrow — re-staff his jobs without moving any customer's appointment." (previewAbsenceResolve → commitAbsenceResolve)
The same prompts appear on every Crisphive listing and docs page, so what you see here is exactly the first-run experience everywhere.
→ Full overview, tool table and one-click connect: https://crisphive.com/claude
Related MCP server: Servicialo
Requirements
Any MCP client that supports remote servers over Streamable HTTP — claude.ai, Claude Desktop, Claude Code, ChatGPT, Gemini CLI, Cursor, VS Code, Windsurf, Cline, Zed, LM Studio, ….
Installation
claude.ai / Claude Desktop (OAuth — no key needed)
Settings → Connectors → Add custom connector, paste
https://api.crisphive.com/mcp. Sign in as the Crisphive business owner when
the consent screen opens. (Custom connectors require a Claude plan that
supports them.)
Claude Code
# OAuth (you'll be prompted to authorize in the browser)
claude mcp add --transport http crisphive https://api.crisphive.com/mcp
# or with an API key (sandbox key shown — safe to experiment)
claude mcp add --transport http crisphive https://api.crisphive.com/mcp \
--header "Authorization: Bearer chsk_test_YOUR_KEY"Cursor
Or add to .cursor/mcp.json:
{
"mcpServers": {
"crisphive": { "url": "https://api.crisphive.com/mcp" }
}
}VS Code
code --add-mcp '{"name":"crisphive","url":"https://api.crisphive.com/mcp"}'ChatGPT
Settings → Connectors (developer mode) → add MCP server with URL
https://api.crisphive.com/mcp (OAuth).
Gemini CLI
Add to ~/.gemini/settings.json (note: Gemini CLI uses httpUrl for
Streamable HTTP servers):
{
"mcpServers": {
"crisphive": {
"httpUrl": "https://api.crisphive.com/mcp",
"headers": { "Authorization": "Bearer chsk_test_YOUR_KEY" }
}
}
}Other MCP clients (Windsurf, Cline, Zed, LM Studio, …)
Most clients accept the standard remote-server shape:
{
"mcpServers": {
"crisphive": {
"url": "https://api.crisphive.com/mcp",
"headers": { "Authorization": "Bearer chsk_test_YOUR_KEY" }
}
}
}Only the URL field name varies in a few clients:
Client | Config file | URL field |
Cline / Roo Code |
|
|
Windsurf |
|
|
Gemini CLI |
|
|
Zed |
|
|
Clients that only speak stdio can bridge with
mcp-remote:
{
"mcpServers": {
"crisphive": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.crisphive.com/mcp"]
}
}
}Local server (npm — @crisphive/mcp)
This repository also ships a thin local stdio server: the same 67 tools
(same names, same schemas — generated from the same /v1 OpenAPI spec as the
hosted endpoint), where each call is an HTTPS request to the Crisphive API
with your key. No business logic runs locally.
{
"mcpServers": {
"crisphive": {
"command": "npx",
"args": ["-y", "@crisphive/mcp"],
"env": { "CRISPHIVE_API_KEY": "chsk_test_YOUR_KEY" }
}
}
}Environment variables:
Variable | Required | Meaning |
| for tool calls |
|
| no | API origin override (default |
Prefer the hosted remote server (https://api.crisphive.com/mcp) when your
client supports it — OAuth, no key handling, always current. The local package
exists for stdio-only clients and self-hosted setups.
Developing in this repo: npm ci && npm test. The tool registry
(src/tools.generated.json) is generated — npm run generate refreshes it
from the live spec; CI fails if it drifts from /v1.
Authentication
Every request is authenticated with a secret API key sent as a bearer token. Create keys from your Crisphive business dashboard. The key prefix selects the data environment:
chsk_live_…→ live (production) datachsk_test_…→ sandbox (isolated test) data
Load keys from the environment — never commit them.
Keys expire. The lifetime is chosen when the key is created — 30 days by
default, up to 365 — and is fixed for that key's life; it cannot be extended
later. To renew, create a second key, point your agent at it, then revoke the
first: a business can hold several active keys at once, so the changeover has
no downtime and needs no special endpoint (the same procedure AWS documents for
access keys). Read expires_at from the dashboard or the key API and schedule
the swap. An aged-out key fails with API_KEY_EXPIRED, distinct from
API_KEY_INVALID, so you can alert on a missed renewal separately from a
revocation.
Crisphive emails the business's owners 7 days before a key expires (14 days for
an OAuth connection), so an expiry should not be a surprise — but the mail goes
to the business, not necessarily to you, so track expires_at yourself. A key
deliberately created for less than 7 days gets no advance notice; it would have
arrived at creation.
The MCP endpoint additionally supports OAuth 2.1 for end-user connectors (claude.ai, ChatGPT, …): the business owner authorizes your agent on a consent screen and no key is ever handled. A compliant MCP client runs the whole flow automatically — discovery, dynamic client registration, authorization code + PKCE. Full flow, scopes and token lifetimes: docs/integration.md.
Tools
67 tools, one per operation of the public /v1 API — same names as the SDK
methods (listCustomers, createJobRequest, …), derived from the same OpenAPI
spec so REST and MCP never drift. Full reference:
docs/tools.md.
Group | Tools |
Customers (CRM sync, full CRUD) |
|
Bookings (create & track) |
|
Catalog (reads) |
|
Catalog management (import sync) |
|
Team & fleet (reads) |
|
Team roster management (HR-system sync) |
|
Matching & scheduling (read-only, engine-computed) |
|
Scheduling actions (drive the schedule) |
|
Priority & emergency dispatch (P0–P3, SLA, cascade) |
|
Absence resolve (sick call — re-staff a technician's whole day) |
|
Technician time off (record a sick day / leave, read existing leave, delete it when they come back) |
|
Webhooks (REST-hook triggers: subscribe / unsubscribe) |
|
Typical agent flow:
listSkills / listJobTypes → discover reference IDs
createCustomer → { customer_id }
listJobRequestBookingWindows → offer only the returned windows
createJobRequest → booking created
quoteJobRequest → confirmJobRequest → scheduled (auto or forced technician)
(quote answers 409 JOB_REQUEST_QUOTE_NOT_SCHEDULABLE when the customer
would see no slot — agree a new time, or resend with force: true)
getJobRequest / listJobRequestChanges → track statusOne-call flow for a phone call or an automation (voice agents: connect to
https://api.crisphive.com/mcp?profile=voice for a small tool set):
listCustomers (phone: "+16135550188") → is the caller already a customer?
bookAndConfirmJobRequest → booked + scheduled, or confirmed:false + refusal
(the job then waits in the coordinator's queue)Emergency (P0) flow:
createJobRequest (priority: "p0") → quoteJobRequest
listEmergencyCandidates → ranked techs + crew_recommendation
previewEmergencyReschedule → what moves (or reassigns)
commitEmergencyReschedule → inserted + auto-confirmed
(or preview with an inline `job` probe, then bookAndConfirmJobRequest
with priority p0 + emergency{dispatch: "now"} books and commits in one call)Sick-call flow (a technician is out — re-staff every job on their board at its UNCHANGED time):
previewAbsenceResolve → who takes each job; nothing written
createTechnicianTimeOff → record the sick day (lands pending — enough for the commit)
commitAbsenceResolve → apply exactly the previewed plan, all-or-nothingPagination
List tools accept page / limit and return a meta object (total,
count, per_page, current_page, total_pages).
Idempotency
Create/commit tools (createCustomer, createTechnician, createJobRequest,
confirmJobRequest, commitJobRequestMove, commitEmergencyReschedule, commitAbsenceResolve)
accept an idempotency_key argument so retries never create a duplicate —
pass the same value when retrying.
Errors
Every tool returns the Crisphive response envelope (as text and as
structuredContent): error_code is 0 on success, a stable string on
failure (CUSTOMER_NOT_FOUND, API_KEY_INVALID, …). Match codes, never
message strings.
Documentation
API reference: https://docs.crisphive.com/technical-reference
Webhooks: https://docs.crisphive.com/webhook
For AI (OpenAPI spec + assistant bootstrap): https://docs.crisphive.com/for-ai
Client integration guide: docs/integration.md
Tool reference: docs/tools.md
Privacy & support
Privacy policy: https://crisphive.com/privacy-policy — Crisphive processes the business data reachable through the API (customers, bookings, technicians, fleet) solely to operate the Service; it does not sell personal information. Data is retained while the account is active and shared only with service providers/sub-processors as necessary. An agent connected over MCP acts on behalf of the authorizing business and is scoped to that business's data, environment (live vs sandbox) and granted permissions.
Support: support@crisphive.com
License
Available Tools
53 toolscommitEmergencyRescheduleCommit emergency insert + cascade rescheduleAInspect
Commit emergency insert + cascade reschedule
Applies the cascade previewed by /emergency/preview: assigns the emergency job to the technician and pushes the displaced jobs back (or, with displacement_mode=reassign, re-staffs them onto their previewed alternates first), atomically. Supports Idempotency-Key. The server recomputes the plan under a lock and fences each job on its status_version — if anything changed since the preview it returns 409 EMERGENCY_RESCHEDULE_PLAN_DRIFTED (re-preview). Same body as preview + optional emergency_expected_version. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md).
409 NEXT STEPS: EMERGENCY_RESCHEDULE_PLAN_DRIFTED — the schedule changed between your preview and this commit (another booking/move won a lane): call /preview again, show the fresh plan, then commit. EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — landing window blocked by an immovable anchor (P0/crew/multi-day): another tech or time. Other codes — same remedies as /candidates.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | Yes | Cascade mode: overtime | next_day (must match the preview). | |
| start_at | Yes | Desired start — business-local naive datetime, no offset. Must be in the future and match the preview. | |
| technician_id | Yes | Target technician (must belong to the business). | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) | |
| emergency_job_id | Yes | ID of the P0 job to insert. | |
| displacement_mode | No | Fate of displaced jobs: reschedule (default) or reassign — must match the preview. | |
| expected_move_ids | No | ExpectedMoveIDs — the displaced job IDs the preview returned (echo BOTH data.days[].moves[].job_id AND data.reassignments[].job_id). RECOMMENDED: if the recomputed cascade would touch a different set (a job booked onto the lane since the preview), the commit is rejected with EMERGENCY_RESCHEDULE_PLAN_DRIFTED instead of displacing a job the coordinator never saw. Omit to opt out. | |
| emergency_expected_version | No | Optimistic-lock fence from the previewed job (0 = fence on the server-read version). |
commitJobRequestMoveCommit a schedule-board job moveAInspect
Commit a schedule-board job move
Applies the move previewed by /move/preview: places the job on the technician at the new time and pushes the displaced jobs back, atomically (per-tech advisory lock; the server recomputes the plan and fences each job on its status_version — drift since the preview returns 409 SCHEDULE_MOVE_PLAN_DRIFTED, re-preview). Same body as preview + optional expected_version. See SCHEDULE_BOARD_DESIGN.md.
409 NEXT STEPS: SCHEDULE_MOVE_PLAN_DRIFTED — the schedule changed since your preview (or expected_move_ids no longer match): re-preview, show the fresh plan, commit again. All other codes — same remedies as /move/preview.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID (UUID or short_code) | |
| mode | Yes | Cascade mode: overtime | next_day (must match the preview). | |
| start_at | Yes | New start — business-local naive datetime, no offset. Must match the preview and be in the future. | |
| technician_id | Yes | Target technician (must match the preview). | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) | |
| expected_version | No | ExpectedVersion — the moved job's status_version from the preview (0 = fence on the fresh plan-read value; negatives rejected). | |
| expected_move_ids | No | ExpectedMoveIDs — the displaced job IDs the preview returned (echo data.days[].moves[].job_id). Optional but RECOMMENDED: if the schedule changed so the commit would push a different set (e.g. a job booked onto the target tech since the preview), the commit is rejected with SCHEDULE_MOVE_PLAN_DRIFTED instead of silently pushing an unseen job. | |
| expected_member_ids | No | ExpectedMemberIDs — crew moves only: the FULL member set the preview staffed (echo data.members[].technician_id). If the commit's re-plan would staff a DIFFERENT set (a previewed replacement got booked in the meantime), the commit is rejected with SCHEDULE_MOVE_PLAN_DRIFTED — crew swaps are never approved unseen. Omit to opt out. |
confirmJobRequestConfirm a booking on behalf of the customerAInspect
Confirm a booking on behalf of the customer
Fires the customer-actor confirm_booking action from the BUSINESS surface (audited as business_on_behalf). Two uses: (1) LIVE — staff confirm a slot for a customer who booked by phone; (2) SANDBOX — the customer magic-token surface is live-only (a sandbox job's link can never reach a real customer), so this is the ONLY way to drive a sandbox test job past booking (book → quote → confirm → assign → complete). Body carries the customer-chosen scheduled_at (business-local naive datetime).
DECISION TABLE — every 409 this endpoint returns, and the correct NEXT STEP (branch on error_code, never on the HTTP status):
• JOB_REQUEST_STAGE_CONFLICT — the job changed since you read it (NOTE: every FAILED confirm attempt also bumps status_version by design). Next: re-GET the job, retry with the fresh status_version.
• JOB_REQUEST_ACTION_NOT_PENDING — the job is no longer at the confirm step (usually: already confirmed). Next: re-GET and show current status; do not retry.
• JOB_REQUEST_NO_TECHNICIAN_AVAILABLE — the TIME is infeasible for everyone (outside working hours / the customer window, or nobody qualifies). Next: pick another time via booking-windows / time-segments. NOT an emergency case — displacement cannot conjure capacity.
• JOB_REQUEST_TECH_INFEASIBLE — the FORCED technician can never take the job then; data.reason says why: cannot_arrive_in_time (commute/shift-start — data.earliest_feasible_at (RFC3339 UTC) is the first same-day time they CAN be on site → offer it) | missing_required_skills | not_available_today | not_lead_tier. Next: keep the tech and reschedule to earliest_feasible_at+, OR keep the time and drop technician_id (auto-pick) / choose another tech from time-segments. NOT an emergency case.
• JOB_REQUEST_P0_REQUIRES_DISPLACEMENT — the ONLY code that routes to the EMERGENCY flow: the job is P0, the tech qualifies, but the lane is genuinely occupied. Next: POST emergency/candidates → preview → commit (the commit auto-confirms). Caveat: if the occupying jobs are themselves P0 the preview will reject with EMERGENCY_RESCHEDULE_SLOT_OCCUPIED (P0 never displaces P0) — then pick another tech/time.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID | |
| scheduled_at | No | Chosen start time — business-local naive datetime, no offset (the business_time.datetime value from the time-segments picker). The server converts to UTC using the job's business timezone. | |
| technician_id | No | TechnicianID (BUSINESS confirm only — ignored on the customer surface): force-assign the job to this technician instead of the ranked auto-pick. Ranking is bypassed; feasibility (hours/time-off/geo/skills), the TierLead rule and the double-booking guard still apply — an infeasible forced tech rejects the confirm (P0 gets the displacement hint). | |
| status_version | No | Optimistic-lock fence: the status_version from your last read. Omitted/0 = fence on the row's current version (no race protection). | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) | |
| arrival_window_minutes | No | ArrivalWindowMinutes = width (phút) của arrival-window ô khách bấm ở slot-picker (chính là time_slot_step_minutes, mặc định 30). Persist để post-confirm detail render lại đúng window. Optional; bounds ([5, 240], khớp slot-picker step) validate ở usecase — single authority, một error code (JOB_REQUEST_INVALID_INPUT). |
createCustomerCreate a customerAInspect
Create a customer
Creates a customer record — the client/account profile a job request (work order) is booked against; use it to import or sync customers from your own CRM, website lead form or intake flow. Address (street, city, postal_code, ...) and coordinates (latitude/longitude) live under the nested address object. service_area_id must be a valid service area UUID belonging to this business.
| Name | Required | Description | Default |
|---|---|---|---|
| uid | No | Your external reference for this customer (your own system's ID). Optional; max 32 chars. | |
| tier | No | Loyalty tier. Defaults to "regular" if omitted. | |
| No | Email address. Optional, but at least one of phone/email is required. | ||
| phone | No | Phone number in E.164 international format: a leading `+` and the country code, e.g. `+16135550188`. A bare national number (`6135550188`) is REJECTED with PHONE_INVALID — there is no default region to guess the country from. Separators (spaces, dashes, parentheses) are stripped before validation. Optional, but at least one of phone/email is required; 10–20 chars. | |
| address | No | Postal address and coordinates. | |
| full_name | Yes | Customer's full name. Required; max 255 chars. | |
| sms_opt_in | No | SMS consent: set true ONLY when the customer explicitly agreed to receive SMS (e.g. ticked a non-pre-checked consent box, or gave verbal/written consent you keep a record of). SMS notifications are suppressed while false. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) | |
| service_area_id | No | UUID of the service area for this customer. Must belong to this business. | |
| preferred_technician_id | No | UUID of the technician this customer prefers. Must belong to this business. |
createJobRequestCreate a job requestAInspect
Create a job request
Books a field-operations job — the work order that enters the dispatch & scheduling pipeline. Send the customer's UUID plus requested job_dates (date + morning/afternoon/evening periods, ideally offered from GET /job-requests/booking-windows), optional job_type_id (service catalog), skill_ids (required technician qualifications) and a free-text description. Quoting, technician/crew assignment and completion then advance the work order through the business's workflow.
| Name | Required | Description | Default |
|---|---|---|---|
| priority | No | Scheduling priority. Optional; omitted bookings receive the business's default_priority setting (assignment settings, default p2). p0=emergency (interrupt-driven insert), p1=top (displaced only by p0), p2=standard, p3=deferrable (first candidate for displacement). | |
| job_dates | Yes | Requested date(s) + period(s) the customer wants the job. At least one, up to 12. | |
| skill_ids | No | UUIDs of the skills the customer desires for this job. Optional; up to 20. | |
| x_timezone | No | Customer IANA timezone (sent as the X-Timezone header) | |
| customer_id | Yes | UUID of an existing customer of this business to book the job for. Required. | |
| description | No | Free-text description of the work requested. Optional; max 2000 chars. | |
| job_type_id | No | UUID of the job type to classify this job. Optional; null leaves the job unclassified. | |
| sla_deadline | No | SLA deadline (business-local naive datetime, e.g. "2030-06-14T17:00:00"). Optional; ONLY valid together with priority=p1 — arms the auto-escalation clock (the job escalates to p0 as breach risk crosses the business's safety buffer). Must be in the future. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key returns the original booking instead of creating a duplicate (sent as the Idempotency-Key header) |
createJobTypeCreate a job typeAInspect
Create a job type
Adds a new job type to the business catalog
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Job-type name (required) — what the booking form shows as the kind of work. Stored as the canonical English value; per-locale names are a dashboard authoring feature. | |
| status | No | Lifecycle status. Defaults to active; inactive types cannot be selected for new job requests. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) |
createServiceAreaCreate a new service areaAInspect
Create a new service area
Creates a service area with optional polygon boundary for the business
| Name | Required | Description | Default |
|---|---|---|---|
| city | No | City name | |
| name | Yes | Area name (required) | |
| county | No | County name | |
| country | No | Country name | |
| boundary | No | GeoJSON polygon boundary (optional) | |
| district | No | District name | |
| description | No | Optional description | |
| postal_code | No | Postal code | |
| state_province | No | State / province name | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) |
createSkillCreate a skillAInspect
Create a skill
Creates a skill under the given category. New skills are active by default. Skill names must be unique within their category.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Skill category ID (UUID) | |
| name | Yes | Skill name (required). The matching engine hard-filters technicians on skills when a booking carries skill_ids. | |
| description | No | What the skill covers. Optional. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) |
createSkillCategoryCreate a skill categoryAInspect
Create a skill category
Creates a skill category for the current business. Categories group skills (e.g. "Plumbing", "Electrical"). Names must be unique within a business.
| Name | Required | Description | Default |
|---|---|---|---|
| icon | No | Icon slug for the dashboard's catalog tree. Optional. | |
| name | Yes | Category name (required), e.g. a trade: Plumbing, Electrical, HVAC. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) |
createTechnicianAdd a technicianAInspect
Add a technician
Creates a technician membership under the current business. If the phone/email matches an existing user, their account is linked. Otherwise a new user identity is created. Either way the membership starts active. The new member is notified (live mode only, best-effort): an email when email is supplied, an SMS when phone is supplied, both when both — informational only, no activation step (login stays passwordless: magic link / OTP). Re-adding someone: if the person is currently SUSPENDED on this business (a dashboard action — not reachable via this API), the create REACTIVATES that existing membership (same technician id, their existing group; also notified). A technician REMOVED with deleteTechnician is a closed membership: re-adding the same email/phone links the SAME underlying person (no duplicate identity) but creates a FRESH membership with a NEW id — history stays under the old one. Owner/Administrator groups cannot be assigned via API key, and not at all in sandbox mode.
Optional relations (all validated; any missing id → 404 TECHNICIAN_NOT_FOUND with missing_ids): buddy_ids sets this technician's buddy list (use when creating a lead); lead_ids adds this technician as a buddy of each named lead (use when creating a buddy — the buddy-side way to attach the same lead↔buddy relation); service_area_ids assigns the technician to those service areas.
start_location_type=office snapshots the business address + coordinates into the technician at create time; address, start_location_lat, start_location_long in the body are ignored. Requires the business to have coordinates set (else 400 BUSINESS_LOCATION_MISSING). start_location_type=home (or empty) uses the address + coordinates from the body.
| Name | Required | Description | Default |
|---|---|---|---|
| No | Email address. At least one of phone/email is required (identity resolution key). | ||
| phone | No | Phone number in E.164 international format: a leading `+` and the country code, e.g. `+16135550188`. A bare national number (`6135550188`) is REJECTED with PHONE_INVALID — there is no default region to guess the country from. At least one of phone/email is required (identity resolution key). | |
| address | No | Home address (the day-start point when start_location_type=home). | |
| lead_ids | No | Leads this technician is a buddy of (set when creating a buddy; the technician is appended to each lead's buddy list). Optional; max 50 technician ids. | |
| buddy_ids | No | Buddies of this technician (set when creating a lead). Optional; max 50 technician ids. | |
| full_name | Yes | The person's full display name. Required; max 255 chars. | |
| job_title | No | Display title (e.g. "Senior HVAC Technician"). | |
| join_date | No | First working day (YYYY-MM-DD). | |
| assignment_tier | No | Crew tier the matching engine assigns by: lead (can head a job), buddy (crew helper), float (excluded from auto crew-assign). | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) | |
| service_area_ids | No | Service areas to assign the technician to. Optional; max 50. Discover via GET /service-areas. | |
| business_group_id | Yes | Role group for the new member. Discover IDs via GET /permission/groups (Owner/Administrator groups are rejected in sandbox mode). | |
| start_location_lat | No | Explicit day-start latitude in decimal degrees (-90..90); when set it wins over the address geocode. | |
| start_location_long | No | Explicit day-start longitude in decimal degrees (-180..180); when set it wins over the address geocode. | |
| start_location_type | No | Where the technician starts their day: home (their address) or office (the business location). Drives the engine's travel estimates. |
createVehicleCreate a new vehicleCInspect
Create a new vehicle
Creates a vehicle for the business fleet
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Vehicle display name (required). The only mandatory field — a fleet import needs nothing but names to get vehicles into the system. | |
| year | No | Model year. Optional. | |
| brand | No | Manufacturer / make. Optional. | |
| model | No | Model name. Optional. | |
| owner_id | No | UUID of the technician who owns (has claimed) this vehicle. Optional. The owner must be a lead technician or a management role — a buddy- or float-tier profile is refused with VEHICLE_OWNER_TIER_NOT_ALLOWED. Assigning which vehicles a technician USES is a different relation: PUT /technicians/{id}/vehicles. | |
| plate_number | No | License plate. Optional. | |
| vehicle_type | No | ||
| current_mileage | No | Odometer reading. Optional; must be >= 0 when sent. | |
| idempotency_key | No | Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE. (sent as the Idempotency-Key header) |
deleteCustomerDelete a customerADestructiveIdempotentInspect
Delete a customer
Soft-deletes a customer record, removing it from the active customer directory; existing bookings keep their customer snapshot.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Customer ID |
deleteJobTypeDelete a job typeADestructiveIdempotentInspect
Delete a job type
Soft-deletes a job type. System rows cannot be deleted.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job Type ID |
deleteServiceAreaDelete a service areaBDestructiveIdempotentInspect
Delete a service area
Soft deletes a service area
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Service Area ID |
deleteSkillDelete a skillADestructiveIdempotentInspect
Delete a skill
Permanently deletes a skill. Returns SKILL_HAS_MEMBERS (409) if any active technicians are still assigned — unassign all technicians first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Skill ID (UUID) |
deleteSkillCategoryDelete a skill categoryADestructiveIdempotentInspect
Delete a skill category
Permanently deletes a skill category. Returns SKILL_CATEGORY_NOT_EMPTY (409) if any skills still belong to the category — remove or move all skills first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Skill category ID (UUID) |
deleteTechnicianRemove a technicianBDestructiveIdempotentInspect
Remove a technician
Soft-removes a technician from the business by setting status to deactive
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID |
deleteVehicleDelete a vehicleADestructiveIdempotentInspect
Delete a vehicle
Soft deletes a vehicle
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Vehicle ID |
getCustomerGet a customerARead-onlyInspect
Get a customer
Returns the full customer record: profile, contact details, tier and lifetime spending summary — a 360° client view for support, upsell or CRM enrichment. contact.preferred_technician includes {id, name}. contact.service_area includes {id, name}. contact.address.latitude / contact.address.longitude are null if no coordinates saved.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Customer ID (UUID) |
getJobRequestGet a job requestARead-onlyInspect
Get a job request
Returns the full work order: current workflow status, quoted duration, confirmed schedule, customer contact snapshot and the assigned technician / crew — everything a dispatcher or an external field-operations system needs to track one job.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID (UUID or short_code) |
getJobRequestTimelineJob timelineARead-onlyInspect
Job timeline
Per-status progress of a job's lifecycle (e.g. booked → confirmed → on the way → arrived → completed, following the business's configured workflow) — render it as a job-tracking timeline. Each status carries its state (completed | current | upcoming), when the job entered it, and the actions fired within it. entered_at may be null for upcoming steps and for older jobs predating the backfill.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID (UUID or short_code) |
getJobTypeGet a job typeARead-onlyInspect
Get a job type
Returns one entry of the business's service catalog (job/work-order type) with its localized display name — e.g. an HVAC tune-up, drain cleaning or electrical inspection offering.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job Type ID |
getServiceAreaGet a service areaARead-onlyInspect
Get a service area
Returns one service area — a geographic coverage zone (service territory) the business operates in, with its name and geometry metadata. Reference its UUID as service_area_id on customer records for territory-aware dispatch.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Service Area ID |
getTechnicianGet a technicianARead-onlyInspect
Get a technician
Returns one technician's full profile: contact info, employment status, assignment tier, skills/qualifications, buddy (crew) relations and assigned vehicles — the dispatch-ready view of a field worker.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID |
getTechnicianScheduleOne technician's real schedule (sessions + time off)ARead-onlyInspect
One technician's real schedule (sessions + time off)
The technician's ACTUAL occupancy over a date range: every job session on their lane (solo/lead and crew) plus approved time-off blocks. Weekly recurring working hours come from the technician-availability endpoints — combine both for the full availability picture ("get crew availability"). from/to are business-local dates (YYYY-MM-DD, inclusive); omitted = today .. +7 days; range max 31 days.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID | |
| to | No | End date (YYYY-MM-DD, inclusive; default from+7d; max range 31 days) | |
| from | No | Start date (YYYY-MM-DD, business-local; default today) |
getVehicleGet a vehicleARead-onlyInspect
Get a vehicle
Returns one fleet vehicle (service van/truck): identity, plate, operational status (idle, on job, maintenance) and which technicians use it — the fleet-management view of a single asset.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Vehicle ID |
listCrewCandidatesMatching crew candidates for a jobARead-onlyInspect
Matching crew candidates for a job
RE-STAFFING candidates for a CONFIRMED, SCHEDULED job (not yet completed/archived) — any earlier or later stage returns 409 JOB_REQUEST_INVALID_TRANSITION. This is the pool of technicians who could REPLACE the current crew: the currently assigned lead and buddies are deliberately excluded (they are the status quo, not an option), so on a small roster an empty leads list is a normal answer, not an error. For pre-booking discovery ("who could take this job before it is confirmed?") use listJobRequestBookingWindows / listMatchingSlots / the time-segments grid instead. Candidates are matched and ranked by the smart-assignment engine — skills per crew slot, weekly availability, existing schedule, time off and travel are all checked; each carries a score breakdown (distance, travel, matched skills) plus the exact on-site session plan they would work. NOT a raw roster list (use GET /technicians for that). Returns the ranked feasible LEAD pool by default; pass include_buddies=true to also return per-slot buddy pools, include_vehicle=true to include the available-vehicle list. force_lead_id checks one specific technician: returns only that lead (with their crew combo) if feasible, else 409 JOB_REQUEST_NO_TECHNICIAN_AVAILABLE.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID or short_code | |
| force_lead_id | No | Check a specific technician as lead — returns only that lead if feasible, else 409 | |
| include_buddies | No | Also return buddy candidate pools | |
| include_vehicle | No | Also return the available-vehicle list |
listCustomersList customersARead-onlyInspect
List customers
Returns a paginated, searchable directory of the business's customer records — the customer database (CRM) behind every booking and work order. Supports the since/next_since cursor for incremental sync into an external CRM, ERP or marketing tool.
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | Search name, UID, phone, email | |
| page | No | Page number (default 1) | |
| sort | No | Sort: created_at_desc|created_at_asc|name_asc|name_desc|uid_asc|uid_desc (default: created_at_desc) | |
| tier | No | Filter by tier: regular|vip (repeatable) | |
| limit | No | Page size (default 15, max 1000) | |
| since | No | RFC3339 cursor for short-polling; echo back next_since from prior response | |
| status | No | Filter by status: active|inactive | |
| preferred_technician_id | No | Filter by preferred technician UUID |
listEmergencyCandidatesRank technicians for a P0 emergency insertAInspect
Rank technicians for a P0 emergency insert
Returns the technicians who could take the emergency job at the requested start, ranked FASTEST-ARRIVAL first (arrival beats route efficiency for a P0). The response also carries a historical crew_recommendation (median crew size on comparable completed jobs + mandatory disclaimer — AC-2). Booked technicians are still candidates — each entry carries the displacement preview (which lower-priority jobs would be pushed, per day) that committing to them would cause; total_moves=0 means a free slot. P0 jobs are never displaced; P1 only by a P0. ETA is estimated from the technician's start location (no live GPS). Feed the chosen technician_id into emergency/preview + emergency/commit.
409 NEXT STEPS: EMERGENCY_RESCHEDULE_NOT_ELIGIBLE — the job cannot be emergency-inserted (not P0, already started/completed/archived, or not quoted): fix the job state or use a normal confirm. EMERGENCY_RESCHEDULE_CREW_UNSUPPORTED — crew jobs cannot use the emergency flow (v1): staff via confirm/reassign instead. EMERGENCY_RESCHEDULE_MULTIDAY_UNSUPPORTED — a confirmed multi-day job cannot be re-inserted (v1): use the normal reassign flow. EMERGENCY_RESCHEDULE_NO_WORKING_DAY — the chosen date has no working hours: pick a working day. EMERGENCY_RESCHEDULE_IN_PAST — start time already passed: pick a future time.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | Yes | Cascade mode each candidate's displacement preview assumes: overtime = displaced jobs stay same-day; next_day = overflow rolls to the next working day. | |
| limit | No | Max candidates to return (default 5, max 10). | |
| start_at | Yes | Desired start — business-local naive datetime, no offset. Must be in the future. | |
| idempotency_key | No | Optional idempotency key (forwarded as the Idempotency-Key header). Reuse the same value when retrying so the operation runs at most once. | |
| emergency_job_id | Yes | ID of the P0 job to place. |
listJobRequestBookingWindowsBooking availabilityARead-onlyInspect
Booking availability
Real-time appointment availability from the scheduling engine: returns the bookable date + time-period windows given technician capacity, working hours and service-territory coverage. Call this before creating a job request and offer the customer ONLY the returned windows — it prevents unschedulable bookings.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | End YYYY-MM-DD | |
| from | No | Start YYYY-MM-DD | |
| x_timezone | Yes | Customer IANA timezone (sent as the X-Timezone header) |
listJobRequestChangesPoll for new & changed job requests (sync feed)ARead-onlyInspect
Poll for new & changed job requests (sync feed)
Keep an external system (your CRM, ERP or field-operations tool) in sync with bookings WITHOUT re-listing everything: returns the job requests (work orders) whose state changed (created, status transition, reschedule, soft-delete/archive) at or after the since cursor, ordered oldest-change-first (updated_at ASC).
How to use it: (1) On your first poll OMIT since — the server primes the cursor at "now", returns no items and a next_since. (2) Store next_since and pass it as since on the next poll. (3) Apply each returned item to your store by UPSERTING on id (the server re-scans a ~5s safety window, so the same job may appear again — never blindly append). (4) If has_more is true the page filled to limit and more changes are already waiting — poll again immediately; otherwise wait your normal interval (e.g. 5–15s).
This is NOT pagination — it is a time-keyed change feed. Use the paginated GET /job-requests for the initial bulk load, then this endpoint to stay live. Filters (status_keys, customer_id, …) narrow the feed to the slice you care about.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max changes per poll (default 15, max 1000). If the page fills, has_more=true. | |
| since | No | RFC3339 cursor from the prior response's next_since. OMIT on the first poll to prime the cursor at server-now. | |
| priority | No | Priority filter (p0|p1|p2|p3) | |
| customer_id | No | Only changes to this customer's jobs (UUID) | |
| status_keys | No | Comma-separated status slugs — only surface changes to jobs in these statuses | |
| scheduled_to | No | Filter to (YYYY-MM-DD = end of that day in the business timezone, or RFC3339), exclusive | |
| technician_id | No | Only changes to jobs assigned to this technician (UUID) | |
| scheduled_from | No | Filter from (YYYY-MM-DD = start of that day in the business timezone, or RFC3339); range is [from, to) |
listJobRequestsList job requestsARead-onlyInspect
List job requests
Paginated list of the business's bookings (work orders) with dispatch-oriented filters: workflow status, customer, assigned technician, scheduled date range and free-text search over code/description. This is also the SCHEDULE query: combine technician_id + scheduled_from/scheduled_to to read one technician's agenda for a day or week (e.g. "what is Alex doing tomorrow"), or just the date range for the whole team's calendar.
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | Search short_code or description (case-insensitive, partial match) | |
| page | No | Page number | |
| sort | No | Sort key: created_at:desc (default) | created_at:asc | scheduled_at:asc | scheduled_at:desc | priority:asc (P0 first) | priority:desc | |
| limit | No | Page size | |
| status | No | active (default) | archived | all | |
| priority | No | Priority filter (p0|p1|p2|p3) | |
| customer_id | No | Customer UUID | |
| status_keys | No | Comma-separated status slugs | |
| completed_to | No | Filter by completion time to (YYYY-MM-DD = end of that day in the business timezone, or RFC3339), exclusive | |
| scheduled_to | No | Filter to (YYYY-MM-DD = end of that day in the business timezone, or RFC3339), exclusive | |
| technician_id | No | Technician UUID | |
| completed_from | No | Filter by completion time from (YYYY-MM-DD = start of that day in the business timezone, or RFC3339); range is [from, to). Counts jobs by when they were completed regardless of reschedules. | |
| scheduled_from | No | Filter from (YYYY-MM-DD = start of that day in the business timezone, or RFC3339); range is [from, to) | |
| service_area_id | No | Service-area UUID (board zone filter) |
listJobTypesList job typesARead-onlyInspect
List job types
Returns the business's service catalog — the job/work-order types it offers (e.g. installation, repair, maintenance, inspection for trades like HVAC, plumbing, electrical, cleaning). Use it to discover the job_type_id accepted when booking a job request, or to render a services menu on your own site.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | Filter by status (active|inactive) |
listMatchingSlotsMatching time slots for a quoted jobARead-onlyInspect
Matching time slots for a quoted job
Bookable arrival-window slots for a quoted job, computed by the smart-assignment matching engine: each slot lists the technicians actually available to start then (skills, weekly availability, existing schedule, time off and travel all checked), with a per-technician match score. Use it to find and offer appointment times an agent or integration can then confirm (POST /job-requests/{id}/confirm with the slot's business_time.datetime). Same grid the end-customer's slot picker shows; slot width defaults to the business's arrival window — override via ?step_minutes (5–240). The job must be quoted first (the quote sets the visit duration the matcher schedules).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID (UUID or short_code) | |
| step_minutes | No | Slot step in minutes (default: business arrival window, 5–240) |
listNearbyTechniciansFind nearby feasible technicians (job-less location query)ARead-onlyInspect
Find nearby feasible technicians (job-less location query)
Ranks who could serve a hypothetical visit at (lat,lng) starting at for duration_minutes — the engine applies the REAL hard filters (weekly hours, existing schedule, approved time-off, geographic service areas, optional skill floor) and returns candidates nearest-arrival first. ETA origin is each technician's start location (no live GPS). Use before creating a booking to propose realistic arrivals.
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | Visit start (RFC3339, e.g. 2026-07-20T14:00:00Z; default now) | |
| lat | Yes | Latitude of the service location | |
| lng | Yes | Longitude of the service location | |
| limit | No | Max candidates (default 10, max 20) | |
| skill_ids | No | Comma-separated skill UUIDs to require/match | |
| duration_minutes | No | Visit length in minutes (default 60; 15–480) |
listServiceAreasList service areasARead-onlyInspect
List service areas
Returns the business's geographic coverage: paginated service areas (service territories / coverage zones) used for routing jobs to the right teams. Discover the service_area_id values accepted on customer create/update here.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (default 1) | |
| limit | No | Items per page (default 15, max 1000) |
listSkillCategoriesList skill categoriesARead-onlyInspect
List skill categories
Returns paginated skill categories — how the business groups technician qualifications by trade or specialty (e.g. HVAC, plumbing, electrical) — ordered alphabetically.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (default: 1) | |
| limit | No | Page size (default: 15, max: 1000) |
listSkillsList all skillsARead-onlyInspect
List all skills
Returns the flat list of all active technician skills / qualifications for the current business — the vocabulary the dispatch engine uses for skill-based matching when assigning technicians and crews. Use it to discover the skill UUIDs accepted in skill_ids when creating a job request. (For a category-grouped view, use GET /skill-categories and GET /skill-categories/{id}/skills.)
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
listSkillsByCategoryList skills in a categoryARead-onlyInspect
List skills in a category
Returns paginated skills (technician qualifications/certifications) belonging to the given trade/specialty category, ordered alphabetically. The members field on each skill is the count of active technicians currently holding it — a quick capacity check per capability.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Skill category ID (UUID) | |
| page | No | Page number (default: 1) | |
| limit | No | Page size (default: 15, max: 1000) |
listTechniciansList techniciansARead-onlyInspect
List technicians
Returns the field workforce roster: paginated technicians (field workers / engineers) with status, assignment tier (lead, buddy, float), skills and crew relations — the people the dispatch engine schedules onto jobs. Discover the preferred_technician_id accepted on customer records here. Supports the since cursor for incremental workforce sync.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (default 1) | |
| sort | No | Sort: created_at_desc|created_at_asc|name_asc|name_desc (default: created_at_desc) | |
| limit | No | Items per page (default 15, max 1000) | |
| since | No | RFC3339 cursor for short-polling; echo back next_since from prior response | |
| status | No | Filter by status (active, onboarding, deactive) | |
| keyword | No | Search by full name, email, phone, or address | |
| assignment_tier | No | Filter by assignment tier (lead, buddy, float) |
listTechnicianSkillsList skills for a technicianARead-onlyInspect
List skills for a technician
Returns paginated skills assigned to the technician. By default (eligible_only omitted or true) only active skills are returned — pass eligible_only=false to include inactive skills.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID — business_user_profiles.id (UUID) | |
| page | No | Page number (default: 1) | |
| limit | No | Page size (default: 15, max: 1000) | |
| eligible_only | No | true (default) = active skills only; false = all assigned skills including inactive |
listVehiclesList vehiclesARead-onlyInspect
List vehicles
Returns the business's fleet: paginated service vehicles (vans/trucks) with operational status (idle, on job, maintenance) — the fleet inventory behind crew carpooling and job mobilization. Supports the since cursor for incremental fleet sync.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (default 1) | |
| limit | No | Items per page (default 15, max 1000) | |
| since | No | RFC3339 cursor for short-polling; echo back next_since from prior response | |
| status | No | Filter by status (inactive, idle, on_job, maintenance) | |
| keyword | No | Search by name, brand, model, or plate number |
previewEmergencyReschedulePreview emergency insert + cascade rescheduleBInspect
Preview emergency insert + cascade reschedule
Computes (WITHOUT writing) the cascade of inserting an emergency job onto a technician at a chosen time: where the emergency lands + every job pushed back, grouped per business-local day. displacement_mode=reassign instead hands each displaced job to another feasible technician at its ORIGINAL window (same-day promise) — jobs with no alternate capacity fall back to reschedule and stay in days. mode=overtime keeps everyone same-day (tech works late); mode=next_day rolls overflow to the next working day(s). Read-only — safe to call repeatedly; commit is a separate endpoint. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md).
409 NEXT STEPS: EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — the landing window is blocked by a job the cascade may NOT move (another P0, a crew or multi-day job): choose another technician (walk the /candidates ranking) or another time; displacement never touches P0/crew/multi-day anchors. EMERGENCY_RESCHEDULE_NOT_ELIGIBLE / CREW_UNSUPPORTED / MULTIDAY_UNSUPPORTED / NO_WORKING_DAY / IN_PAST — same remedies as /candidates.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | Yes | Cascade mode: overtime = displaced jobs stay same-day (tech works late); next_day = overflow rolls to the next working day. | |
| start_at | Yes | Desired start — business-local naive datetime, no offset. Must be in the future. | |
| technician_id | Yes | Target technician (must belong to the business). | |
| idempotency_key | No | Optional idempotency key (forwarded as the Idempotency-Key header). Reuse the same value when retrying so the operation runs at most once. | |
| emergency_job_id | Yes | ID of the P0 job to insert. | |
| displacement_mode | No | Fate of displaced jobs: reschedule (default — pushed to later windows) or reassign (handed to another feasible technician at their ORIGINAL time; no-capacity jobs fall back to reschedule). |
previewJobRequestMovePreview a schedule-board job moveAInspect
Preview a schedule-board job move
Computes (WITHOUT writing) the outcome of moving a confirmed job to a new time and/or technician: where it lands, every later job pushed back per mode, and the warnings the coordinator would accept (displaced jobs leaving their confirmed windows, overtime). Same technician = pure time move; different technician = manual reassign. Read-only — safe to call repeatedly while dragging; commit is a separate endpoint. See SCHEDULE_BOARD_DESIGN.md.
Warning detail: a TECH_NOT_FEASIBLE warning carries reason = cannot_arrive_in_time (commute from the tech day-start location / shift start; earliest_feasible_at (RFC3339 UTC) is the first same-day time they CAN be on site — suggest it as the drop slot) | missing_required_skills | not_available_today (no working hours, approved time off, or outside the service area) | not_lead_tier. For a P0 move this warning is advisory (coordinator may commit anyway); for p1/p2/p3 the same condition is the hard 409 SCHEDULE_MOVE_TECH_INFEASIBLE.
409 NEXT STEPS: SCHEDULE_MOVE_NOT_ELIGIBLE (job unconfirmed/unquoted/archived/completed — not movable) · SCHEDULE_MOVE_IN_PROGRESS (tech already executing — do not move) · SCHEDULE_MOVE_IN_PAST (pick a future time) · SCHEDULE_MOVE_SLOT_OCCUPIED (landing window blocked by an immovable anchor — another tech/time) · SCHEDULE_MOVE_TECH_INFEASIBLE (non-P0 hard block: target tech not qualified/available — its data carries technician_id, reason (same catalog as the TECH_NOT_FEASIBLE warning) and, for cannot_arrive_in_time, earliest_feasible_at (RFC3339 UTC) to suggest as the drop slot; change tech or time) · SCHEDULE_MOVE_MULTIDAY_UNSUPPORTED (multi-day jobs not movable v1) · SCHEDULE_MOVE_NO_WORKING_DAY (pick a working day) · SCHEDULE_MOVE_REQUIRES_FREE_SLOT (non-P0 moves may not displace — free capacity only, unless the owner enables allow_non_p0_displacement) · SCHEDULE_MOVE_CREW_UNSTAFFABLE (a crew slot has no feasible replacement at the new time — another time). A landing outside the customer-confirmed window is NOT an error — it returns 200 with a MOVED_OUTSIDE_WINDOW warning (customer_window attached) that the coordinator overrides.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID (UUID or short_code) | |
| mode | Yes | Cascade mode for displaced jobs: overtime = stay same-day (tech works late); next_day = overflow rolls to the next working day. | |
| start_at | Yes | New start — business-local naive datetime, no offset. Must be in the future. | |
| technician_id | Yes | Target technician — may equal the current tech (pure time move) or differ (manual reassign). | |
| idempotency_key | No | Optional idempotency key (forwarded as the Idempotency-Key header). Reuse the same value when retrying so the operation runs at most once. |
quoteJobRequestFire quote (FIXED action — business)AInspect
Fire quote (FIXED action — business)
Sends the quote: sets quoted_at + duration cols, advances pending_action to confirm_booking. Status stays booking.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID | |
| crew | No | Crew — multi-person plan (tech lead + buddies). Omit / empty = single person (lead 100%). When present must have exactly one is_lead and wrench_percent summing to 100. See MULTIPERSON_CREW_DESIGN.md. | |
| status_version | No | Optimistic-lock fence: the status_version from your last read. Omitted/0 = fence on the row's current version (no race protection). | |
| idempotency_key | No | Optional idempotency key (forwarded as the Idempotency-Key header). Reuse the same value when retrying so the operation runs at most once. | |
| job_duration_minutes | Yes | Hands-on work duration in minutes (man-minutes for a crew job). Required, min 1. | |
| mobilization_minutes | No | Mobilization (setup/travel-prep) minutes added before the work. Optional, min 0. | |
| demobilization_minutes | No | Demobilization (teardown) minutes added after the work. Optional, min 0. |
replaceTechnicianBuddiesReplace a technician's buddiesAIdempotentInspect
Replace a technician's buddies
Overwrites the technician's buddy list with the provided set of technician IDs. Sending an empty list clears all buddies. Each buddy ID must be an active technician of the same business; a technician cannot be their own buddy.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID | |
| buddy_ids | No | Technician ids to set as this lead's buddies (max 50; self-buddy rejected). |
replaceTechnicianLeadsReplace a buddy's leaders (buddy side)AIdempotentInspect
Replace a buddy's leaders (buddy side)
Sets the full set of leads this buddy belongs to (many-to-many favorites). Replace-semantics — lead_ids is the complete new list; [] clears every leader. Managed by business staff.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Buddy technician ID | |
| lead_ids | No | Lead technician ids this buddy assists (max 50). |
replaceTechnicianServiceAreasReplace a technician's service areasAIdempotentInspect
Replace a technician's service areas
Overwrites the technician's service-area assignments with the provided set of service area IDs. Sending an empty list clears all. Each service area ID must belong to the same business — any missing id → 404 SERVICE_AREA_NOT_FOUND with missing_ids and no writes. The resolved set is returned and also embedded as service_areas in the technician GET/list response. Managed by business staff (Booking Coordinator), not tech self-service.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID | |
| service_area_ids | No | Service-area ids (max 50). Discover via GET /service-areas. |
replaceTechnicianSkillsReplace a technician's skillsAInspect
Replace a technician's skills
Sets the technician's full skill set in one call (replace semantics): skills not in the list are removed, new ones added. Pass an empty list to clear all. All skills must be active and belong to the business — on SKILL_NOT_FOUND (404) the data field contains {"missing_ids": ["uuid", ...]}; on SKILL_INACTIVE (409) it contains {"inactive_ids": ["uuid", ...]}.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID — business_user_profiles.id (UUID) | |
| skill_ids | No | Skill ids (max 100). Discover via GET /skills. |
replaceTechnicianVehiclesReplace a technician's vehiclesAIdempotentInspect
Replace a technician's vehicles
Overwrites the technician's vehicle list with the provided set of vehicle IDs (the vehicles this technician uses). Sending an empty list clears all. Each vehicle ID must belong to the same business. The list is also embedded as vehicle_ids in the technician GET/list response.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID | |
| vehicle_ids | No | Vehicle ids (max 50). Discover via GET /vehicles. |
updateCustomerUpdate a customerAIdempotentInspect
Update a customer
PARTIAL update — send only the fields you are changing; anything you OMIT is left exactly as stored (two-way CRM sync friendly: push one field from your system of record without re-sending the record). To CLEAR a field, send it as an empty string: uid, phone, email, notes, preferred_technician_id, service_area_id. tier and status are enums with no empty member, so an empty value there is ignored rather than stored. full_name cannot be set to empty. The nested address object is all-or-nothing: omit it to leave the stored address (and its coordinates) untouched; when present it REPLACES the whole block, and missing latitude/longitude are geocoded from the address. A customer must keep at least one contact channel — an update that would clear both phone and email is refused with PHONE_OR_EMAIL_REQUIRED.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Customer ID (UUID) | |
| uid | No | Your external reference for this customer. Omit to leave unchanged, "" to clear. Max 32 chars. | |
| tier | No | Loyalty tier. Omit to leave unchanged; "" is ignored (an enum has no empty member). | |
| No | Email address. Omit to leave unchanged, "" to clear. | ||
| notes | No | Free-form internal notes about the customer. Omit to leave unchanged, "" to clear; max 4000 chars. | |
| phone | No | Phone number in E.164 international format (`+16135550188`); a bare national number is rejected with PHONE_INVALID — see createCustomerReq.Phone. Omit to leave unchanged, "" to clear. 10–20 chars. | |
| status | No | Lifecycle status. Omit to leave unchanged; "" is ignored. | |
| address | No | Postal address and coordinates. Omit the whole object to leave the stored address untouched; when present it REPLACES the address block. | |
| full_name | No | Customer's full name. Omit to leave unchanged; an empty or blank value is ignored (a customer cannot be left nameless). Max 255 chars. | |
| sms_opt_in | No | SMS consent, tri-state: omit/null = leave unchanged; true = grant (only when the customer explicitly consented — the original consent timestamp is preserved); false = revoke (opt-out, stops SMS immediately). | |
| service_area_id | No | UUID of the service area for this customer. Omit to leave unchanged, "" to clear. Must belong to this business. | |
| preferred_technician_id | No | UUID of the technician this customer prefers. Omit to leave unchanged, "" to clear. Must belong to this business. |
updateJobPrioritySet job priority (scheduling staff)AInspect
Set job priority (scheduling staff)
Sets the P0–P3 priority on a non-archived, non-completed job (Owner / Administrator / Booking Coordinator). Allowed values: "p0" (emergency, interrupt-driven) | "p1" (top — displaced only by p0; may carry an sla_deadline arming auto-escalation) | "p2" (standard) | "p3" (deferrable, first displacement victim). sla_deadline is only valid with p1 and must be in the future (business-local naive datetime); moving away from p1 disarms the SLA clock. Accepts UUID or short_code in :id.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Job request ID or short_code | |
| note | No | Optional justification (max 500 chars) — recorded on the activity trail and the priority_changed webhook. Recommended when de-escalating an SLA-escalated job (audit provenance). | |
| priority | Yes | New P0–P3 priority level. p0=emergency, p1=top, p2=standard, p3=deferrable. | |
| sla_deadline | No | SLA deadline (business-local naive datetime) — only with priority=p1; (re)arms the auto-escalation clock. Omitted on a p1 change keeps any stored deadline; changing away from p1 disarms SLA entirely. |
updateTechnicianUpdate a technicianAIdempotentInspect
Update a technician
Updates mutable technician profile fields. start_location_type=office re-snapshots the current business address + coordinates (body address/start_location_lat/start_location_long ignored; requires business coordinates, else 400 BUSINESS_LOCATION_MISSING). start_location_type=home (or empty) uses the address + coordinates from the body.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Technician ID | |
| No | Email address. At least one of phone/email is required. | ||
| phone | No | Phone number in E.164 international format (`+16135550188`); a bare national number is rejected with PHONE_INVALID — see AddTechnicianReq.Phone. At least one of phone/email is required — enforced in the usecase; see the note on AddTechnicianReq.Phone for why no binding rule may decide it. | |
| address | No | Home address (the day-start point when start_location_type=home). | |
| full_name | Yes | The person's full display name. Required; max 255 chars. | |
| job_title | No | Display title. | |
| join_date | No | First working day (YYYY-MM-DD). | |
| assignment_tier | No | Crew tier: lead / buddy / float (float = excluded from auto crew-assign). | |
| business_group_id | Yes | Role group. Discover IDs via GET /permission/groups. | |
| start_location_lat | No | Explicit day-start latitude in decimal degrees (-90..90); when set it wins over the address geocode. | |
| start_location_long | No | Explicit day-start longitude in decimal degrees (-180..180); when set it wins over the address geocode. | |
| start_location_type | No | Day-start point: home or office; explicit coordinates win when set. |
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
14 tool updates
v0.1.1- Changed
createCustomer1 field changed- changed
Input schema / properties / phone / descriptionPrevious value: -"Phone number. Optional, but at least one of phone/email is required; 10–20 chars."New value: +"Phone number in E.164 international format: a leading `+` and the country\ncode, e.g. `+16135550188`. A bare national number (`6135550188`) is\nREJECTED with PHONE_INVALID — there is no default region to guess the\ncountry from. Separators (spaces, dashes, parentheses) are stripped before\nvalidation. Optional, but at least one of phone/email is required; 10–20 chars."
- Added
createJobType - Added
createServiceArea - Added
createSkill - Added
createSkillCategory - Changed
createTechnician1 field changed- changed
Input schema / properties / phone / descriptionPrevious value: -"At least one of phone/email is required (identity resolution key)."New value: +"Phone number in E.164 international format: a leading `+` and the country\ncode, e.g. `+16135550188`. A bare national number (`6135550188`) is\nREJECTED with PHONE_INVALID — there is no default region to guess the\ncountry from. At least one of phone/email is required (identity resolution key)."
- Added
createVehicle - Added
deleteJobType - Added
deleteServiceArea - Added
deleteSkill - Added
deleteSkillCategory - Added
deleteVehicle - Changed
updateCustomer11 fields changed- changed
Input schema / properties / address / descriptionPrevious value: -"Postal address and coordinates."New value: +"Postal address and coordinates. Omit the whole object to leave the stored address untouched; when present it REPLACES the address block." - changed
Input schema / properties / email / descriptionPrevious value: -"Email address."New value: +"Email address. Omit to leave unchanged, \"\" to clear." - changed
Input schema / properties / full_name / descriptionPrevious value: -"Customer's full name. Required; max 255 chars."New value: +"Customer's full name. Omit to leave unchanged; an empty or blank value is ignored (a customer cannot be left nameless). Max 255 chars." - changed
Input schema / properties / notes / descriptionPrevious value: -"Free-form internal notes about the customer; max 4000 chars."New value: +"Free-form internal notes about the customer. Omit to leave unchanged, \"\" to clear; max 4000 chars." - changed
Input schema / properties / phone / descriptionPrevious value: -"Phone number. 10–20 chars."New value: +"Phone number in E.164 international format (`+16135550188`); a bare\nnational number is rejected with PHONE_INVALID — see createCustomerReq.Phone.\nOmit to leave unchanged, \"\" to clear. 10–20 chars." - changed
Input schema / properties / preferred_technician_id / descriptionPrevious value: -"UUID of the technician this customer prefers. Must belong to this business."New value: +"UUID of the technician this customer prefers. Omit to leave unchanged, \"\" to clear. Must belong to this business." - changed
Input schema / properties / service_area_id / descriptionPrevious value: -"UUID of the service area for this customer. Must belong to this business."New value: +"UUID of the service area for this customer. Omit to leave unchanged, \"\" to clear. Must belong to this business." - changed
Input schema / properties / status / descriptionPrevious value: -"Lifecycle status."New value: +"Lifecycle status. Omit to leave unchanged; \"\" is ignored." - changed
Input schema / properties / tier / descriptionPrevious value: -"Loyalty tier."New value: +"Loyalty tier. Omit to leave unchanged; \"\" is ignored (an enum has no empty member)." - changed
Input schema / properties / uid / descriptionPrevious value: -"Your external reference for this customer. Optional; max 32 chars."New value: +"Your external reference for this customer. Omit to leave unchanged, \"\" to clear. Max 32 chars." - changed
Input schema / requiredPrevious value: -[ - "id", - "full_name" -]New value: +[ + "id" +]
- Changed
updateTechnician1 field changed- changed
Input schema / properties / phone / descriptionPrevious value: -"At least one of phone/email is required."New value: +"Phone number in E.164 international format (`+16135550188`); a bare\nnational number is rejected with PHONE_INVALID — see AddTechnicianReq.Phone.\nAt least one of phone/email is required — enforced in the usecase; see\nthe note on AddTechnicianReq.Phone for why no binding rule may decide it."
43 tool updates
v0.1.0- First observed
commitEmergencyReschedule - First observed
commitJobRequestMove - First observed
confirmJobRequest - First observed
createCustomer - First observed
createJobRequest - First observed
createTechnician - First observed
deleteCustomer - First observed
deleteTechnician - First observed
getCustomer - First observed
getJobRequest - First observed
getJobRequestTimeline - First observed
getJobType - First observed
getServiceArea - First observed
getTechnician - First observed
getTechnicianSchedule - First observed
getVehicle - First observed
listCrewCandidates - First observed
listCustomers - First observed
listEmergencyCandidates - First observed
listJobRequestBookingWindows - First observed
listJobRequestChanges - First observed
listJobRequests - First observed
listJobTypes - First observed
listMatchingSlots - First observed
listNearbyTechnicians - First observed
listServiceAreas - First observed
listSkillCategories - First observed
listSkills - First observed
listSkillsByCategory - First observed
listTechnicians - First observed
listTechnicianSkills - First observed
listVehicles - First observed
previewEmergencyReschedule - First observed
previewJobRequestMove - First observed
quoteJobRequest - First observed
replaceTechnicianBuddies - First observed
replaceTechnicianLeads - First observed
replaceTechnicianServiceAreas - First observed
replaceTechnicianSkills - First observed
replaceTechnicianVehicles - First observed
updateCustomer - First observed
updateJobPriority - First observed
updateTechnician
TDQS
Scored across 53 tools
Most tools pair a unique action with a unique resource, but the scheduling/candidate cluster—listJobRequestBookingWindows, listMatchingSlots, listNearbyTechnicians, listCrewCandidates, and listEmergencyCandidates—has overlapping concepts that could cause misselection. The preview/commit and quote/confirm pairs are distinguishable but demand close reading.
All 53 tools use the same verb-first camelCase pattern (create/get/list/update/delete plus preview/commit/replace/quote), with no snake_case or inconsistent verb styles. Minor semantic quirks like listTechnicianSkills versus listSkillsByCategory do not break the predictable pattern.
With 53 tools, the surface is in the extreme range and creates a heavy tool-selection and context burden for agents. Even though the domain is broad, this set should be split into focused servers (customers, scheduling, technicians, fleet, catalog) rather than exposed as one monolithic tool set.
Scheduling and emergency-rescheduling flows are richly covered, but several resources are missing standard updates (no updateVehicle, updateJobType, updateServiceArea, updateSkill, or updateSkillCategory), and job requests lack explicit cancel/archive/advance-status endpoints despite a lifecycle-driven design. These are significant gaps an agent will hit when trying to manage work orders end to end.
Maintenance
Related MCP Connectors
Headless field service software. Your AI agent runs jobs, schedules, quotes, invoices and payments.
Field workforce scheduling for AI agents. GPS punches, forms, timesheets. No dashboard.
Agentic-AI route optimization for delivery fleets, capacity- and time-window-safe.
Scheduling infrastructure for AI agents across Google and Microsoft calendars.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceScheduling and booking engine for AI agents. Check availability, hold slots, and confirm appointments with two-phase booking and conflict-free resource management.2Apache 2.0
- AlicenseAqualityBmaintenanceOpen protocol for AI-agent coordination of professional services. Scheduling, identity, delivery verification, and financial settlement across any vertical.10103 npm1Apache 2.0

ThinAir Geoofficial
AlicenseAqualityBmaintenanceLocation & routing intelligence for AI agents — geocoding, truck routing, traffic, weather, and place search.1932 npm1MIT- AlicenseAqualityAmaintenanceThe infrastructure for AI teams: a self-hosted server that gives a fleet of agents shared semantic memory, tasks, direct messages, and session handoff. Any agent that speaks HTTP participates: Claude Code, AutoGen, raw API scripts, anything.479MIT