# Authentication and permissions REST API v1 and MCP accept Bearer credentials. Use a workspace API key for server integrations or OAuth for a connected AI client. Public [client forms](/docs/client-forms) use form/workspace slugs instead of credentials. ## API keys Create a REST API key in your workspace's integration settings, select the scopes needed, and save the secret when it is shown. Keys use `wk_.`. The workspace is derived from the key, and its owner is the acting user for writes. ```bash curl 'https://my.bluesuite.app/api/v1/contacts' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" ``` Keep REST/general keys in your server or trusted client's secret store. Rotate by creating a replacement, updating the integration, and revoking the old key. Older keys without an associated owner must be recreated for v1. ## Scopes For the resource prefixes below, `:read` permits the listed reads and `:write` permits the listed writes. Each endpoint states the exact scope combination; a write scope does not automatically include read access. | Prefix | Resources | | --------------------- | -------------------------------------------- | | `contacts` | Contacts and contact tasks | | `properties` | Contact properties | | `requests` | Requests and conversion | | `quotes` | Quotes | | `quote-templates` | Quote templates | | `jobs` | Jobs and job expenses | | `events` | Events and schedules | | `invoices` | Invoices and payments | | `timesheets` | Timesheets and time entries | | `equipment` | Company equipment, tasks, notes | | `installed_equipment` | Installed units, notes, reminders, job links | | `service_plans` | Service plans and subscriptions | | `tags` | Tags and assignments | | `reviews` | Review requests | | `services` | Service catalog | | `tax-rates` | Tax rates | | `users` | Workspace users | | `files` | Upload capability, plus parent entity scope | Additional scopes include `workspace:read`, `notifications:write`, `webhooks:read`, `webhooks:write`, `forms:read`, `forms:write`, and `custom_fields:read`. Notice that scope spellings are not always identical to URL paths: service plans and installed equipment use underscores. Comments use the parent's scope. Uploads need both a files scope and the parent's scope, except workspace line-item images, which need only the files scope. Lead creation needs contact, property, and request write scopes. Schedule/hold operations can require both job and event write access. General `read` and `write` scopes act as wildcards for their respective actions. REST/fine-grained keys have no automatic resource access unless scopes are selected. | Key type | Default scopes | | ----------------------- | -------------------------------------------------------------- | | Embed forms | `forms:read`, `forms:write`, `webhooks:read` | | WordPress | `custom_fields:read`, `forms:read`, `forms:write` | | Zapier | `forms:read`, `forms:write`, `webhooks:read`, `webhooks:write` | | REST API / fine-grained | Selected scopes | | General | `read`, `write` | ## OAuth and workspace selection For MCP, connect to `https://my.bluesuite.app/api/mcp` with a client supporting Streamable HTTP and OAuth discovery. Complete the authorization flow and choose the intended workspace. The OAuth client is mapped to that selected workspace; if the mapping is missing, reauthorize. The protected-resource metadata is at: ```text https://my.bluesuite.app/.well-known/oauth-protected-resource/api/mcp ``` Use the authorization server advertised there rather than hardcoding one. See [MCP setup](/docs/mcp) for transport details. OAuth requests are subject to workspace membership, roles, feature permissions, and data visibility; an integration should not promise every user the same results. REST v1 also accepts OAuth/session JWTs. A mapped OAuth token resolves the selected workspace. An internal session-token integration supplies `x-workspace-id`; membership is verified. API-key requests derive their workspace from the key and do not need this header. ## Troubleshooting | Result | Check | | -------------------------------------------- | ----------------------------------------------------------------- | | `401` | Bearer header, expired/revoked OAuth token, or authorization flow | | `403 Invalid API key or insufficient scopes` | Key validity and required endpoint scopes | | Owner-association error | Recreate the API key with a current workspace user | | Module not enabled | Enable the relevant feature in the workspace | | OAuth workspace mapping missing | Reauthorize and select a workspace | | `429` | Follow `Retry-After` and the rate-limit headers | Legacy webhook endpoints can still accept old key formats with an explicit `workspace_id`. This does **not** make old-format keys valid for REST v1 or MCP. Use a `wk_` key for new server integrations. # Getting started Connect your software or AI assistant to the same contacts, jobs, visits, and billing records your team uses in BlueSuite. ## Choose a connection | Use case | Start here | | ----------------------------------------------- | ------------------------------------------------------------------------ | | Build a server integration | [REST API v1](/docs/rest-api) and [authentication](/docs/authentication) | | Connect an AI assistant | [MCP setup](/docs/mcp) and the [tool reference](/docs/mcp/tools) | | Receive changes from BlueSuite | [Webhooks](/docs/webhooks) | | Capture inquiries or offer booking on a website | [Client forms](/docs/client-forms) | | Maintain an older form integration | [Legacy embedded forms](/docs/forms) | ## Make your first API request Create a REST API key in your workspace's integration settings with the scopes your integration needs. Store it in your server's environment as `BLUESUITE_API_KEY`. ```bash curl 'https://my.bluesuite.app/api/v1/contacts?per_page=10' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" ``` This request needs `contacts:read`. Responses are scoped to the workspace belonging to the key. An API key is not needed for the public client-form endpoints. ## Addresses | Connection | URL | | ---------------------------- | ----------------------------------------- | | REST API | `https://my.bluesuite.app/api/v1` | | MCP, Streamable HTTP | `https://my.bluesuite.app/api/mcp` | | Documentation for AI readers | [llms-full.txt](/llms-full.txt) | | REST request schemas | [api-reference.json](/api-reference.json) | | MCP input and output schemas | [mcp-tools.json](/mcp-tools.json) | ## Before writing data Creating a quote or invoice does not send it. Recording a payment does not charge a card. Recurring job schedules control both future visits and per-visit billing. Read the relevant resource guide or [MCP workflows](/docs/mcp/workflows) before automating these operations. # Client Forms Client Forms are published forms created in the BlueSuite dashboard. Use the provided embed snippet or hosted form link for a ready-made interface. A custom renderer can use the public APIs below. No workspace API key belongs in these requests. The form must be published and its workspace/features available. Use the workspace and form slugs from its public link. Load the current definition rather than hardcoding field IDs. ## Read form configuration ```http GET /api/client-forms/config?workspace=:workspaceSlug&form=:formSlug ``` Returns `200` with `success: true` and `data` containing: * `form`: name, description, instructions, and `bookingEnabled`. * `fields`: field IDs, types, labels, helper text, required flags, options, and service IDs. * `services` and `theme`: offered services and resolved branding. * `booking`: slot interval settings, or null. * `upload`: upload URL, public gateway key, limits, and accepted types, or null. * `submit`: form submission/confirmation settings. Missing slugs return 400. An unavailable or unpublished form returns 404. ## Read appointment availability ```http GET /api/client-forms/slots?workspace=:workspaceSlug&form=:formSlug ``` Optionally pass `services=1,2` for the selected offered service IDs; they affect appointment duration. Returns `{ "success": true, "data": { "days": [...] } }`. Use the returned day/time values for booking. Availability is not cached and is rechecked at submission. A form without booking enabled returns 404 here. Changing selected services requires fetching new availability. ## Submit answers ```http POST /api/client-forms/submit ``` Send JSON: | Field | Required | Meaning | | ----------- | --------------------- | -------------------------------------------------------------------- | | `workspace` | Yes | Workspace slug | | `form` | Yes | Form slug | | `values` | Yes | Up to 200 objects with a positive `form_field_id` and string `value` | | `booking` | When required by form | Object with `date` (YYYY-MM-DD) and `time` (HH:MM), or null | | `website` | No | Honeypot, leave empty | | `source` | No | Use `embed` for an embedded renderer | Every answer is serialized as a string. Text/date/number fields use their text value. Checkboxes use `true` / `false`; a required checkbox must be checked. Service pickers use comma-separated service IDs. Address fields contain serialized address JSON. Signatures use serialized `{ "type": "typed", "value": "Jamie Rivera" }` or a supported drawn PNG signature. File fields contain the uploaded storage path. Field IDs, required answers, choices, and service selections are validated against the published form. Do not invent an answer for a missing required field. ```json { "workspace": "your-workspace", "form": "request-service", "values": [ {"form_field_id": 12, "value": "Jamie Rivera"}, {"form_field_id": 13, "value": "jamie@example.com"} ], "booking": null, "website": "", "source": "embed" } ``` Replace these field IDs and answers with the form's current definition. The maximum individual answer is 751,000 characters, with a 2,000,000-character aggregate limit; field-specific validation can be stricter. Success returns `201` with `success: true` and the submission result in `data`, including the confirmation message. Validation returns 400. A time that was booked by someone else returns `409` with `code: "SLOT_TAKEN"`; refresh availability and let the customer choose again. Do not silently pick another appointment. ## Upload a file Use the `upload.url` and rules supplied by config. Send multipart fields `file`, `workspace`, and `form`. When an `anonKey` is provided, send it in both `Authorization: Bearer ...` and `apikey` headers; this is the public gateway key, not a workspace secret. Accepted files are JPEG, PNG, GIF, WebP, HEIC, and PDF, up to 5 MB. The upload returns `{ "path": "..." }`. Store that returned path as the file field's answer, then submit the form. Do not use the authenticated REST upload endpoint for anonymous form visitors. ## Rate limits and CORS Public config and availability reads allow 120 requests per hour per workspace/IP for each read route. Submissions allow 20 per hour per workspace/IP. A limit returns 429. These APIs allow cross-origin form use; keep the honeypot empty and present server validation errors to the customer. # Legacy embedded forms This API is retained for existing v1 embed snippets. Use [Client Forms](/docs/client-forms) for new website forms and booking flows, or [Leads](/docs/rest-api/leads) for authenticated server-to-server inquiry capture. ## Submit an existing embed form ```http POST /api/forms/embed-request ``` Send JSON to `https://my.bluesuite.app/api/forms/embed-request`. This endpoint reads the API key from `apiKey` in the body or `api_key` in the query, **not** from the Bearer header. Use a dedicated embed key with `forms:write`. Old-format keys also require `workspaceId`. | Field | Required | Meaning | | ------------- | --------------------- | ---------------------- | | `apiKey` | Unless in query | Embed API key | | `workspaceId` | For legacy key format | Positive workspace ID | | `formData` | Yes | Object described below | | formData field | Required | Meaning | | ----------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `firstName`, `lastName` | Yes | 1 to 50 characters each | | `email` | Yes | Valid email | | `phone` | Yes | Digits, optional leading +, first digit 1 to 9 | | `description` | Yes | 5 to 1,000 characters | | `company` | No | Up to 100 characters | | `property` | No | Object with required `street` (1 to 100 characters); optional `street2`, `city`, `state`, `zip` | | `customFields` | No | Values keyed by configured custom-field keys | | `website` | No | Honeypot; leave empty | ```json { "apiKey": "wk_example.your_embed_key", "formData": { "firstName": "Jamie", "lastName": "Rivera", "email": "jamie@example.com", "phone": "+15551234567", "description": "Please contact me about annual maintenance.", "website": "" } } ``` The flow resolves/creates the contact, optionally creates a property, and creates a request. The requests module must be enabled. This legacy flow does not create a Client Form submission record. Success is `201` with `{ "success": true, "data": { "requestId": 42, "requestNumber": 10, "contactId": 12, "propertyId": null } }`. Malformed input returns 400, bad credentials 401, a filled honeypot 422, and exceeding 100 submissions per hour per workspace returns 429. Other failures include unavailable workspaces or disabled requests. The endpoint supports browser CORS; use only a dedicated embed key in client-side code. ## Country configuration ```http GET /api/forms/embed-config ``` Supply `api_key` in the query with `forms:read`; old keys also need `workspace_id`. Returns `{ "country": "US" }` using the workspace country (US fallback). The response may be cached for one hour. # MCP setup BlueSuite's Model Context Protocol (MCP) server exposes business operations as tools. Reads and writes use the connected workspace; changes appear in the dashboard and mobile app. Connect your workspace through the **[BlueSuite app in ChatGPT](https://chatgpt.com/plugins/plugin_asdk_app_69bc6ec1082881919e4ecd80a79de3bd)** to look up contacts, plan jobs, and work with quotes and invoices in conversation. This is the recommended setup for ChatGPT users: install the app and sign in with BlueSuite. ## Connect with ChatGPT 1. Open the [BlueSuite ChatGPT app](https://chatgpt.com/plugins/plugin_asdk_app_69bc6ec1082881919e4ecd80a79de3bd). ChatGPT lists it in its **Plugins** directory. 2. Install it using the install or **+** control, then connect the BlueSuite account when prompted. 3. Sign in to BlueSuite, select the workspace you want to use, and review the requested access before authorizing. 4. Start a new chat. Type **@BlueSuite** and select the app to use it in the conversation. 5. Try: **“Use BlueSuite to show my jobs scheduled for tomorrow.”** The published app handles the MCP connection for you. You do not need to enter a server URL or create an API key for this setup. You need an existing BlueSuite account; [create one here](https://my.bluesuite.app/sign-up/account) if needed. Installation and connection controls can vary by ChatGPT account and workspace. If the app is unavailable in a managed workspace, ask your ChatGPT administrator to enable it. See [OpenAI's plugin installation guide](https://learn.chatgpt.com/docs/plugins#install-and-use-a-plugin) for current host instructions. ## Connect with Claude Use a remote custom connector in Claude on the web or Claude Desktop: 1. Open **Customize > Connectors** and choose **Add custom connector**. 2. Name it **BlueSuite** and enter `https://my.bluesuite.app/api/mcp` as the remote MCP server URL. 3. Continue with OAuth authentication and choose **Sign in now**. If Claude asks how to identify the OAuth client, use **Register automatically**. 4. Finish adding the connector, sign in to BlueSuite, and authorize the intended workspace. If it is not connected yet, select **Connect** beside BlueSuite. 5. Enable BlueSuite from the conversation's **+ > Connectors** menu, then ask: **“Use BlueSuite to list my outstanding invoices.”** For Claude Team or Enterprise, an authorized organization owner or administrator must add the connector under **Organization settings > Connectors** before members connect their accounts. See [Claude's custom connector guide](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) for current controls and availability. ## Remote connection Add this server URL to a client that supports **Streamable HTTP**: ```text https://my.bluesuite.app/api/mcp ``` Use OAuth when your host supports discovery. Complete authorization and select a workspace. The server advertises its protected-resource metadata in an unauthenticated response's `WWW-Authenticate` header. You can also read it at `/.well-known/oauth-protected-resource/api/mcp` on the same host. For a trusted client using an API key, configure this request header: ```text Authorization: Bearer wk_. ``` Choose the resource scopes your tools need. Connecting and discovering tools does not grant permission to execute them; underlying API scopes, workspace features, and user permissions still apply. See [authentication](/docs/authentication). ## Transport behavior The remote server is stateless. Send MCP messages over POST with the JSON headers and protocol negotiation your MCP client provides. It does not support the legacy standalone SSE GET transport: GET returns `405`. DELETE also returns `405` because there is no persistent session to terminate. A protocol-level discovery sequence is: 1. Send `initialize` and read the server capabilities and instructions. 2. Complete the MCP initialization handshake. 3. Call `tools/list` to get the current tools and schemas. 4. Use `tools/call` with a tool name and arguments. Use an MCP client library or a host's built-in connector for the handshake. The [tool reference](/docs/mcp/tools) describes every registered tool. If a host caches tool discovery, refresh or reconnect after additions; a published integration can expose an older approved snapshot. ## First call For a concrete request, use the relevant tools directly. If a newly connected user only says hello or asks what they can do, call `get_started` with an empty arguments object. It suggests useful starting actions based on the account. Use `get_workspace` for timezone and workspace context, and resolve names with the appropriate `list_*` tool before passing IDs. See [workflow rules](/docs/mcp/workflows). ## Inputs, results, and errors All tools reject unknown top-level argument names, including tools that take no arguments. Numeric IDs must be positive; user IDs are UUIDs. Some schemas allow numeric-string coercion and date normalization, but callers should send the canonical types and formats shown in the reference. Tool results include text content and matching `structuredContent`, usually shaped as `{ "data": ... }`. Lists can also include `pagination`. Help and onboarding tools return text in `data`; entity records allow additional fields rather than promising a fixed database schema. Check `isError` before interpreting a result as success. Invalid inputs can also produce protocol errors. Field-level messages describe what to correct; do not retry with invented business details. API authorization, state conflicts, and rate limits still apply to tool calls. [MCP schemas as JSON](/mcp-tools.json) include each tool's complete input/output contract. ## Local stdio server Repository-based integrations can run the stdio server locally: ```bash npm run build --workspace=@bluesuite-app/mcp-server BLUEPRO_API_KEY="$BLUESUITE_API_KEY" \ BLUEPRO_API_URL="https://my.bluesuite.app" \ node packages/mcp-server/dist/index.js ``` Run this from an installed checkout. The two environment variable names retain the former product name for compatibility; they must be spelled exactly as shown. Set the URL explicitly: the local server defaults to `http://localhost:3000`. In a host's stdio configuration, use `node` as the command, the absolute path to `packages/mcp-server/dist/index.js` as its argument, and those environment values in its secret/environment configuration. This private workspace package is not an installable public npm package. ## Troubleshooting * A browser GET returning `405` does not mean the MCP server is down. Configure Streamable HTTP POST. * A missing tool can be a cached host snapshot. Refresh discovery. * A discovered tool returning `403` needs the appropriate permissions, scopes, or enabled module. * If the wrong workspace is selected, reconnect and authorize the intended workspace. * For help using the product, use `list_help_guides` and `get_help_article`. # Workflow rules Tools act on real business records. Resolve record names before writes, ask which record is intended when matches are ambiguous, and use the workspace timezone for relative dates. Get UUIDs for assignments from `list_users`. ## Drafting and sending `create_quote` and `create_invoice` create records. To deliver them, call `send_notification` with the record ID as `module_id` and `type: "quote_to_customer"` or `"invoice_to_customer"`. Confirm customer-facing communication before sending. Setting a status to sent is bookkeeping, not delivery. Initial sends move the status for you. Use `reminder: true` for reminders without an initial-send transition. Delivery uses workspace notification settings and SMS consent; the current notification endpoint also requires a contact email. When creating a quote from a template, pass `contact_id` so it is addressed to the intended customer. Quote and invoice tool responses include a client URL when the record's public ID and workspace slug are available. ## Recording money received To settle an invoice, call `create_payment` with: ```json { "object_type": "invoice", "object_id": 101, "category": "payment", "settle_remaining": true } ``` Omit `amount`. The server calculates the balance and records a completed payment. Use the payment method/date supplied by the user; otherwise omit them. Method defaults to Other. Pending payments block settlement. This does not charge a card or send a receipt. A bare paid-status change is rejected while a balance remains. For a batch, report each invoice's outcome rather than claiming all succeeded after a partial failure. ## Recurring work Choose the tool based on the work: | Work | Tool | | -------------------------------------------- | -------------------------------------------------------------- | | Repeating customer visits | Find/create the job, then `setup_recurring_job` | | Repeating internal task/calendar entry | `create_event`, type `task` or `event`, recurrence `recurring` | | Follow-up on a client | `create_contact_task` | | Maintenance on company equipment | `create_equipment_task` | | Stop a job schedule while preserving history | `end_recurring_job_schedule` | Before setting up a recurring job, confirm the repeat pattern, end condition, and billing choice. `per_visit` automatically generates invoices when visits are completed; `manual` leaves billing to the business. Do not guess this choice. Appointments cannot repeat on their own. Creating a job with type recurring is also insufficient: the schedule tool establishes the visit series and billing together. New recurring invoices are unsupported. A custom weekly/monthly pattern needs selected days. A finite duration requires `repeat_duration`; use `repeat_duration_unit: "occurrences"` for a fixed count. For job/event schedules, only include `repeat_frequency_schedule` when `repeat_frequency_unit` is `custom`. Timed visits need start and end times unless all-day. Use `update_event` with the intended `update_scope` when changing occurrences. Ask whether the change applies only to this visit or future work before selecting the scope. ## Holding and resuming jobs Use `hold_job` and confirm whether upcoming appointments should be kept or cancelled. Read the latest job and pass its `held_at` to `resume_job` as `expected_held_at`. A recurring job needs a resume date only when appointments were cancelled and the schedule has not explicitly ended. Kept appointments continue generating. When all schedules have explicitly ended, omit `resume_on`: resuming clears the hold without restarting them. Holds do not support service-plan work. ## Requests and follow-ups For a new person asking for work, `create_new_lead` creates the contact, optional property, and linked request. For an existing request, use `convert_request` to create the job/quote/invoice and mark the request converted. “Remind me to call this customer” is a contact task. Set `reminders_enabled: true` when a notification is wanted; it defaults to false. Use a timezone-bearing due timestamp and resolve the intended time with the user. Completing a recurring task completes that occurrence. ## Service plans and subscriptions A plan is an offering. Creating/editing it charges nobody, and archiving it does not cancel existing subscribers. A subscription represents an enrolled customer and may bill through Stripe. Use `cancel_service_plan_subscription` to stop billing, after confirmation. Inspect `billing_stopped` / `stripe_cancelled`; never report that billing stopped when only the BlueSuite record changed. `force: true` can leave Stripe billing active. There is no MCP subscription-creation or per-subscriber price/cadence-edit tool. ## Equipment and files Company equipment is the business's own assets; installed equipment belongs at customer properties. Record company equipment service history with `create_equipment_note`, rather than only completing a task. Retire/sell assets when history should remain. Installed equipment automatically schedules warranty/replacement reminders by default. Review `auto_reminders_enabled` before creating a unit with those dates. Use `upload_file` for chat attachments, supported downloadable URLs, or base64 data. Use `list_files` to inspect existing files, and `update_upload` for metadata. Workspace line-item images require `module_type: "workspace"` and `category: "line_item"`. # Choosing a request API For new integrations, choose one of these supported paths: | Need | API | | -------------------------------------------- | ------------------------------------------- | | Create an inquiry for a new contact | [POST /api/v1/leads](/docs/rest-api/leads) | | Create/manage requests for existing contacts | [REST v1 requests](/docs/rest-api/requests) | | Capture a website inquiry or booking | [Client Forms](/docs/client-forms) | | Keep an existing v1 form snippet working | [Legacy embedded forms](/docs/forms) | The older `POST /api/requests` endpoint is a restricted WordPress integration using a deployment-managed credential. It does not accept ordinary workspace API keys and is not the public request API for new integrations. Use the v1 lead endpoint to create and link a contact, optional property, and request with workspace scopes. Use request conversion to turn an inquiry into a job, quote, or invoice and mark it converted. # Webhooks Webhooks POST event data to your application. Subscription management uses the API-key flow under `https://my.bluesuite.app/api/webhooks`, separate from REST v1. ## Subscribe ```http POST /api/webhooks/subscribe ``` Requires `webhooks:write`. Send JSON with `trigger_event` and an absolute `webhook_url`. Optional fields are `zapier_subscription_id` (string) and `filters` (an object of exact-match data-field filters). ```bash curl 'https://my.bluesuite.app/api/webhooks/subscribe' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"trigger_event":"new_request","webhook_url":"https://example.com/hooks/bluesuite"}' ``` Returns `200` with `success: true` and a `subscription` object containing its numeric ID, event, URL, active state, creation date, and optional Zapier subscription ID. Supported subscription events: | Event | Meaning | | ----------------- | --------------------- | | `new_request` | Request created | | `request_updated` | Request updated | | `new_contact` | Contact created | | `contact_updated` | Contact updated | | `new_quote` | Quote created | | `quote_accepted` | Quote accepted | | `new_job` | Job created | | `new_invoice` | Invoice created | | `invoice_sent` | Invoice moved to sent | | `invoice_paid` | Invoice moved to paid | An invoice status event is not proof of customer delivery or a card charge. See [Invoices](/docs/rest-api/invoices) and [Payments](/docs/rest-api/payments). ## List subscriptions ```http GET /api/webhooks/list ``` Requires `webhooks:read`. Optionally filter by `trigger_event`. Returns `{ "success": true, "subscriptions": [...], "count": 1 }`, including active state, timestamps, URL, filters, and integration ID. It does not return signing secrets. ## Unsubscribe ```http DELETE /api/webhooks/unsubscribe ``` Requires `webhooks:write`. Send `{ "subscription_id": 123 }` in the JSON body. Returns `success` and a confirmation `message`. ## Send a test ```http POST /api/webhooks/test ``` Requires both `webhooks:read` and `webhooks:write`. Send `subscription_id` and `trigger_event`. The test endpoint accepts the subscription events above **except** `request_updated`. This sends an HTTP request to the subscribed URL. The response includes `success`, `status_code`, `response_body`, `error`, and `message`. Inspect `success`; the management request can return HTTP 200 even if the test delivery failed. ## Payload ```json { "event": "new_request", "workspace_id": 123, "timestamp": "2026-10-27T14:00:00.000Z", "data": {"id": 456}, "meta": { "webhook_id": "123", "delivery_attempt": 1, "link": "https://my.bluesuite.app/requests/456" } } ``` `data` contains the event-specific record. `meta.webhook_id` is the **subscription ID**, not a unique event/delivery ID, so do not deduplicate every message on that value alone. Resource links are generated by the server; consume the supplied URL. ## Signatures The current wire headers retain the former product name for compatibility: * `X-BluePro-Signature: sha256=` * `X-BluePro-Event` * `X-BluePro-Workspace-ID` The signature is HMAC-SHA256 over the serialized JSON body using the subscription secret. If you have provisioned that secret, verify against the original request body bytes with a constant-time comparison. The public subscribe/list responses currently omit the secret, so they alone do not provide a self-service signing-secret setup. Do not expect an `X-BlueSuite-Signature` header. ## Delivery and retries Return a 2xx response promptly; outbound delivery times out after 30 seconds. Failed deliveries are retried on a best-effort, in-process schedule (one minute, five minutes, fifteen minutes, then one hour, up to five attempts total). Retries are not a durable queue and can be lost when the process stops. Reconcile critical state through the REST API and make your receiver tolerant of duplicate events. Management errors can use `{ "error": "...", "details": [...] }` without the REST v1 `success` envelope. Missing/invalid credentials return 401, invalid data returns 400, and unexpected failures return 500. Legacy-format keys additionally require a `workspace_id` query parameter. # Comments Post and read internal comments on contacts, requests, quotes, jobs, invoices, events, or timesheets. Every request is scoped to the parent entity. A read requires its read scope (for example, `jobs:read`); creating or deleting a comment requires its write scope. Comments support replies and mentions. Deletion is a soft delete. There is no public comment-update endpoint. Comments are internal discussion, not a customer message; use [Notifications](/docs/rest-api/notifications) for quote/invoice delivery. List comments by `module_type` and `module_id`. The parent must exist in the authenticated workspace. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/comments ```http GET /api/v1/comments ``` **Required scopes:** Determined by the parent entity; see the permissions above.. ### Query parameters | Field | Type / accepted values | Required | Details | | ------------- | ----------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------- | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"` | Yes | Parent entity type. | | `module_id` | integer | Yes | Parent entity ID. | | `page` | integer | No | Page number, starting at 1. | | `per_page` | integer | No | Records per page, from 1 to 100. minimum: 1. maximum: 100. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/comments ```http POST /api/v1/comments ``` **Required scopes:** Determined by the parent entity; see the permissions above.. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------- | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"` | Yes | | | `module_id` | integer | Yes | | | `comment` | string | Yes | minLength: 1. maxLength: 10000. | | `mentioned_user_ids` | array of string | No | maxItems: 50. | | `reply_to` | integer | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/comments/:id ```http GET /api/v1/comments/:id ``` **Required scopes:** Determined by the parent entity; see the permissions above.. **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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/comments/:id ```http DELETE /api/v1/comments/:id ``` **Required scopes:** Determined by the parent entity; see the permissions above.. **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](/docs/rest-api#responses-and-errors). # Contacts, properties, and tasks Contacts represent people and companies; properties represent service locations. Use contact IDs from this workspace and user UUIDs from [Users](/docs/rest-api/users). Contact creation defaults to type `none`; [Leads](/docs/rest-api/leads) defaults a newly created contact to `lead`. Property operations under a contact create, update, or remove that contact's link to a property. Deleting the link does not mean deleting the property everywhere. `GET /properties` searches properties across the workspace. Contact tasks have a title, optional due date and assignee, and optional recurring schedule. Reminders default to off. A recurring task needs a due date; a finite repeat duration needs a count, and a custom weekly/monthly pattern needs selected days. See [recurring work](/docs/mcp/workflows#recurring-work) for the shared pattern fields. Task status uses its dedicated endpoint. When updating a repeating task, choose the appropriate `update_scope`. List contacts with any of a set of tags using `tag_ids=1,2`. A record carrying either tag matches. When no records match, the result is empty. ### Example: create a contact ```bash curl 'https://my.bluesuite.app/api/v1/contacts' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"first_name":"Jamie","last_name":"Rivera","email":"jamie@example.com","type":"customer"}' ``` {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/contacts ```http GET /api/v1/contacts ``` **Required scopes:** `contacts:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `type` | string | No | Filter by type. | | `owner_id` | string | No | Filter by owner UUID from GET /users. | | `tag_ids` | string | No | Comma-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](/docs/rest-api#responses-and-errors). ## POST /api/v1/contacts ```http POST /api/v1/contacts ``` **Required scopes:** `contacts:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ------------------ | | `first_name` | string | No | Default: `""`. | | `last_name` | string | No | Default: `""`. | | `email` | string or null | No | | | `phone` | string or null | No | | | `secondary_email` | string or null | No | | | `secondary_phone` | string or null | No | | | `company` | string or null | No | | | `company_department` | string or null | No | | | `company_position` | string or null | No | | | `title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` or null | No | | | `type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Default: `"none"`. | | `owner_id` | string or null | No | | | `info` | JSON value or null | No | | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/properties ```http GET /api/v1/properties ``` **Required scopes:** `properties:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `property_type` | string | No | Property type. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## GET /api/v1/contacts/:id ```http GET /api/v1/contacts/:id ``` **Required scopes:** `contacts: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/contacts/:id ```http PUT /api/v1/contacts/:id ``` **Required scopes:** `contacts:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ------------------ | | `first_name` | string | No | Default: `""`. | | `last_name` | string | No | Default: `""`. | | `email` | string or null | No | | | `phone` | string or null | No | | | `secondary_email` | string or null | No | | | `secondary_phone` | string or null | No | | | `company` | string or null | No | | | `company_department` | string or null | No | | | `company_position` | string or null | No | | | `title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` or null | No | | | `type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Default: `"none"`. | | `owner_id` | string or null | No | | | `info` | JSON value or null | No | | | `custom_fields` | JSON value or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/contacts/:id ```http DELETE /api/v1/contacts/:id ``` **Required scopes:** `contacts: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](/docs/rest-api#responses-and-errors). ## GET /api/v1/contacts/:id/properties ```http GET /api/v1/contacts/:id/properties ``` **Required scopes:** `properties:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/contacts/:id/properties ```http POST /api/v1/contacts/:id/properties ``` **Required scopes:** `properties:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------------- | ------------------------------------------------------------------------------------------------- | -------- | --------------------------- | | `address` | object | Yes | | | `address.street` | string | No | Default: `""`. | | `address.street_2` | string | No | Default: `""`. | | `address.city` | string | No | Default: `""`. | | `address.state` | string | No | Default: `""`. | | `address.postal_code` | string | No | Default: `""`. | | `address.country` | string | No | Default: `""`. | | `property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Default: `"single-family"`. | | `role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | No | Default: `"owner"`. | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/contacts/:id/tasks ```http GET /api/v1/contacts/:id/tasks ``` **Required scopes:** `contacts:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/contacts/:id/tasks ```http POST /api/v1/contacts/:id/tasks ``` **Required scopes:** `contacts:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------- | | `title` | string | Yes | minLength: 1. | | `description` | string or null | No | | | `due_date` | string or null | No | ISO timestamp. Reminders fire relative to this. | | `assignee_id` | string or null | No | | | `reminders_enabled` | boolean | No | Default: `false`. | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/contacts/:id/properties/:propertyId ```http PUT /api/v1/contacts/:id/properties/:propertyId ``` **Required scopes:** `properties:write`. **Path parameters:** `id`, `propertyId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------------- | ------------------------------------------------------------------------------------------------- | -------- | -------------- | | `address` | object | No | | | `address.street` | string | No | Default: `""`. | | `address.street_2` | string | No | Default: `""`. | | `address.city` | string | No | Default: `""`. | | `address.state` | string | No | Default: `""`. | | `address.postal_code` | string | No | Default: `""`. | | `address.country` | string | No | Default: `""`. | | `property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | | | `role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/contacts/:id/properties/:propertyId ```http DELETE /api/v1/contacts/:id/properties/:propertyId ``` **Required scopes:** `properties:write`. **Path parameters:** `id`, `propertyId`. 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/contacts/:id/tasks/:taskId ```http GET /api/v1/contacts/:id/tasks/:taskId ``` **Required scopes:** `contacts:read`. **Path parameters:** `id`, `taskId`. 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/contacts/:id/tasks/:taskId ```http PUT /api/v1/contacts/:id/tasks/:taskId ``` **Required scopes:** `contacts:write`. **Path parameters:** `id`, `taskId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------- | | `title` | string | No | minLength: 1. | | `description` | string or null | No | | | `due_date` | string or null | No | ISO timestamp. Reminders fire relative to this. | | `assignee_id` | string or null | No | | | `reminders_enabled` | boolean | No | Default: `false`. | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"forever"`. | | `update_scope` | `"single"`, `"all"`, `"recreate"` | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/contacts/:id/tasks/:taskId ```http DELETE /api/v1/contacts/:id/tasks/:taskId ``` **Required scopes:** `contacts:write`. **Path parameters:** `id`, `taskId`. 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/contacts/:id/tasks/:taskId/status ```http PUT /api/v1/contacts/:id/tasks/:taskId/status ``` **Required scopes:** `contacts:write`. **Path parameters:** `id`, `taskId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------ | -------- | ------- | | `status` | `"open"`, `"completed"`, `"overdue"` | 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](/docs/rest-api#responses-and-errors). # Company equipment Track the business's own vehicles, trailers, tools, and machines. Equipment installed at customer properties belongs in [Installed equipment](/docs/rest-api/installed-equipment). If a name is omitted, BlueSuite derives it from the year/make/model, serial number, or category. A replacement date maintains an open replacement task; completed task history is preserved. Tasks support an assignee, due date, reminders, and recurrence. Reminders default to off. Recurring tasks require a due date; finite duration requires a count, and custom weekly/monthly patterns need selected days. Use `update_scope` when editing repeating tasks and the status endpoint to complete or reopen work. Notes form the equipment service log. Use `activity_date` for when the work happened, rather than when you entered it. Date-time values must include a timezone. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/equipment ```http GET /api/v1/equipment ``` **Required scopes:** `equipment:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/equipment ```http POST /api/v1/equipment ``` **Required scopes:** `equipment:write`. ### JSON body | Field | Type / accepted values | Required | Details | | --------------------- | ---------------------------------------------------------- | -------- | -------------------- | | `name` | string | No | | | `category` | `"vehicle"`, `"trailer"`, `"tool"`, `"machine"`, `"other"` | No | Default: `"other"`. | | `status` | `"active"`, `"in_repair"`, `"retired"`, `"sold"` | No | Default: `"active"`. | | `description` | string or null | No | | | `make` | string or null | No | | | `model` | string or null | No | | | `year` | integer or null | No | | | `serial_number` | string or null | No | | | `purchase_date` | string or null | No | | | `purchase_price` | number or null | No | | | `warranty_expiration` | string or null | No | | | `warranty_number` | string or null | No | | | `replacement_date` | string or null | No | | | `assignee_id` | string or null | No | | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/equipment/:id ```http GET /api/v1/equipment/:id ``` **Required scopes:** `equipment: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/equipment/:id ```http PUT /api/v1/equipment/:id ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------------- | ---------------------------------------------------------- | -------- | -------------------- | | `name` | string | No | | | `category` | `"vehicle"`, `"trailer"`, `"tool"`, `"machine"`, `"other"` | No | Default: `"other"`. | | `status` | `"active"`, `"in_repair"`, `"retired"`, `"sold"` | No | Default: `"active"`. | | `description` | string or null | No | | | `make` | string or null | No | | | `model` | string or null | No | | | `year` | integer or null | No | | | `serial_number` | string or null | No | | | `purchase_date` | string or null | No | | | `purchase_price` | number or null | No | | | `warranty_expiration` | string or null | No | | | `warranty_number` | string or null | No | | | `replacement_date` | string or null | No | | | `assignee_id` | string or null | No | | | `custom_fields` | JSON value or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/equipment/:id ```http DELETE /api/v1/equipment/:id ``` **Required scopes:** `equipment: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](/docs/rest-api#responses-and-errors). ## GET /api/v1/equipment/:id/notes ```http GET /api/v1/equipment/:id/notes ``` **Required scopes:** `equipment:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `type` | string | No | Filter by type. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/equipment/:id/notes ```http POST /api/v1/equipment/:id/notes ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------------------- | -------- | ------------------ | | `title` | string | Yes | minLength: 1. | | `content` | string or null | No | | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Default: `"note"`. | | `activity_date` | string or string or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/equipment/:id/tasks ```http GET /api/v1/equipment/:id/tasks ``` **Required scopes:** `equipment:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/equipment/:id/tasks ```http POST /api/v1/equipment/:id/tasks ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | --------------------- | | `title` | string | Yes | minLength: 1. | | `description` | string or null | No | | | `due_date` | string or string or null | No | | | `assignee_id` | string or null | No | | | `reminders_enabled` | boolean | No | Default: `false`. | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/equipment/:id/notes/:noteId ```http PUT /api/v1/equipment/:id/notes/:noteId ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`, `noteId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------------------- | -------- | ------------------ | | `title` | string | No | minLength: 1. | | `content` | string or null | No | | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Default: `"note"`. | | `activity_date` | string or string or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/equipment/:id/notes/:noteId ```http DELETE /api/v1/equipment/:id/notes/:noteId ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`, `noteId`. 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/equipment/:id/tasks/:taskId ```http PUT /api/v1/equipment/:id/tasks/:taskId ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`, `taskId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | --------------------- | | `title` | string | No | minLength: 1. | | `description` | string or null | No | | | `due_date` | string or string or null | No | | | `assignee_id` | string or null | No | | | `reminders_enabled` | boolean | No | Default: `false`. | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"forever"`. | | `update_scope` | `"single"`, `"all"`, `"recreate"` | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/equipment/:id/tasks/:taskId ```http DELETE /api/v1/equipment/:id/tasks/:taskId ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`, `taskId`. 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/equipment/:id/tasks/:taskId/status ```http PUT /api/v1/equipment/:id/tasks/:taskId/status ``` **Required scopes:** `equipment:write`. **Path parameters:** `id`, `taskId`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------ | -------- | ------- | | `status` | `"open"`, `"completed"`, `"overdue"` | 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](/docs/rest-api#responses-and-errors). # Events and visits Manage appointments, tasks, and calendar events. Input dates/times use the workspace timezone and are converted to UTC for storage. Times returned from saved events may therefore differ from the local input. ### Scheduling rules Both `start_time` and `end_time` are required unless `all_day: true`. The end must be after the start. For date-only work, explicitly use `all_day: true`. Flexible visits can use `is_flexible` and `duration_minutes`. Standalone tasks and calendar events can repeat. Appointments repeat through [recurring job schedules](/docs/rest-api/jobs), rather than standalone appointment recurrence. A custom weekly/monthly pattern requires selected days, and a finite repeat duration requires a count. Only send `repeat_frequency_schedule` when `repeat_frequency_unit` is `custom`. On updates, `update_scope` controls which occurrences change. Partial time edits are merged with the existing schedule before validation. Use the dedicated status endpoint for completion/cancellation and `PUT /events/:id/line-items` to replace a visit's line items; this is a complete replacement of that visit's pricing list. ### Example: create a timed appointment ```bash curl 'https://my.bluesuite.app/api/v1/events' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"name":"Site inspection","job_id":42,"type":"appointment","start_date":"2026-11-02","end_date":"2026-11-02","start_time":"09:00","end_time":"10:00"}' ``` {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/events ```http GET /api/v1/events ``` **Required scopes:** `events:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `start_date` | string | No | Inclusive start date, YYYY-MM-DD. | | `end_date` | string | No | Inclusive end date, YYYY-MM-DD. | | `job_id` | string | No | Filter by job ID. | | `type` | string | No | Filter by type. | | `assignee_ids` | string | No | Comma-separated user UUIDs. | | `status` | string | No | Filter by status. | | `request_id` | string | No | Filter by request ID. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/events ```http POST /api/v1/events ``` **Required scopes:** `events:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------- | | `name` | string or null | No | | | `description` | string or null | No | | | `job_id` | integer or null | No | | | `request_id` | integer or null | No | | | `start_date` | string | Yes | | | `end_date` | string | Yes | | | `start_time` | string or null | No | | | `end_time` | string or null | No | | | `all_day` | boolean | No | Default: `false`. | | `multi_day` | boolean | No | Default: `false`. | | `is_flexible` | boolean or null | No | | | `duration_minutes` | integer or null | No | | | `type` | `"appointment"`, `"task"`, `"event"` | No | Default: `"event"`. | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | No | Default: `"scheduled"`. | | `assignee_ids` | array of string | No | | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"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](/docs/rest-api#responses-and-errors). ## GET /api/v1/events/:id ```http GET /api/v1/events/:id ``` **Required scopes:** `events: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/events/:id ```http PUT /api/v1/events/:id ``` **Required scopes:** `events:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------- | | `name` | string or null | No | | | `description` | string or null | No | | | `job_id` | integer or null | No | | | `request_id` | integer or null | No | | | `start_date` | string | No | | | `end_date` | string | No | | | `start_time` | string or null | No | | | `end_time` | string or null | No | | | `all_day` | boolean | No | Default: `false`. | | `multi_day` | boolean | No | Default: `false`. | | `is_flexible` | boolean or null | No | | | `duration_minutes` | integer or null | No | | | `type` | `"appointment"`, `"task"`, `"event"` | No | Default: `"event"`. | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | No | Default: `"scheduled"`. | | `assignee_ids` | array of string | No | | | `recurrence_type` | `"single"`, `"recurring"` | No | Default: `"single"`. | | `repeat_frequency` | integer or null | No | | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"forever"`. | | `update_scope` | `"single"`, `"all"`, `"recreate"` | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/events/:id ```http DELETE /api/v1/events/:id ``` **Required scopes:** `events: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/events/:id/line-items ```http PUT /api/v1/events/:id/line-items ``` **Required scopes:** `events:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------ | ----------------------------------------------- | -------- | ------- | | `line_items` | JSON value or array of object or object or null | 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/events/:id/status ```http PUT /api/v1/events/:id/status ``` **Required scopes:** `events:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------------------------- | -------- | ------- | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | 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](/docs/rest-api#responses-and-errors). # REST API overview The REST API operates on records in one BlueSuite workspace. Each resource page lists the supported operations, required scopes, request fields, and behavior. Some resources are read-only or have dedicated actions rather than unrestricted create/update/delete operations. ## Base URL ```text https://my.bluesuite.app/api/v1 ``` Use a Bearer API key and `Content-Type: application/json` for JSON writes. File uploads use multipart form data. See [authentication](/docs/authentication) for API keys and OAuth. Record IDs in paths are positive integers. User/profile IDs are UUIDs from [Users](/docs/rest-api/users). Keep all related IDs in the same workspace. ## Pagination and filters Most lists accept these parameters; the resource page lists exceptions and additional filters. | Parameter | Default | Meaning | | ---------------- | ------- | ----------------------------------- | | `page` | `1` | Positive page number | | `per_page` | `25` | Page size, 1 to 100 | | `search` | Omitted | Search string, up to 100 characters | | `sort_by` | Omitted | Supported column for the resource | | `sort_direction` | `desc` | `asc` or `desc` | Use `pagination.total_pages` to decide whether to fetch another page. Do not infer the total record count from page size. Quote/job/invoice/request status lists use comma-separated values where documented. Contact/request/quote/job/invoice tag filters match **any** selected tag. Resources with a strict sort-column list reject unsupported values with a field-level error. Other resources fall back to default ordering when the shared sort whitelist does not recognize the column. ## Responses and errors Most successful reads/updates return `200`; creation returns `201`. A single result uses `{ "success": true, "data": ... }`. A paginated list looks like: ```json { "success": true, "data": [{"id": 42, "first_name": "Jamie", "last_name": "Rivera"}], "pagination": {"page": 1, "per_page": 25, "total_pages": 1} } ``` Entity responses may contain expanded related records. Fields vary by operation, workspace features, and permissions. Tag assignments return an unpaginated array. The user PATCH route omits `success`; legacy webhook and form APIs have their own envelopes. Errors normally contain `success: false`, an `error` string, and optional `details`. Validation details identify the affected field path. | Status | Meaning | | ------ | ---------------------------------------------------------------------------------------- | | `400` | Invalid fields, query parameters, IDs, or an unsupported operation | | `401` | Missing/malformed authorization or invalid OAuth/session token | | `403` | Invalid API key, missing scope, disabled module, or insufficient access | | `404` | Record not found in the authenticated workspace | | `409` | State conflict, such as an unpaid invoice marked paid, changed job hold, or locked scope | | `422` | Some operation-specific validation failures | | `429` | Rate limit reached | | `500` | Unexpected server failure | | `502` | An upstream operation failed, such as stopping Stripe subscription billing | Treat error messages and field details as the source for correcting a request. Do not blindly retry writes: most creation endpoints do not offer an idempotency key. A failed multi-step operation can have already created a record. ## Rate limits API-key v1 requests use a limit of **1,000 requests per one-hour window per key**. Responses after rate-limit evaluation include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix seconds). A `429` also includes `Retry-After` in seconds. Honor these headers instead of polling aggressively. Public forms use separate limits. ## Dates, amounts, and updates Use the workspace timezone for local scheduling dates and times. Timed events require both times unless explicitly all-day; timestamps such as payment dates need an offset. Check the resource guide for whether a field is a calendar date, clock time, or timestamp. Monetary amounts use the workspace currency. Time-entry `total_time` is **minutes**, not hours. Missing fields on partial updates are generally preserved; `null` only clears a value where the schema allows it. Many JSON write bodies are strict and reject unknown fields. Schema tables show structural requirements. Cross-field rules, state checks, and permissions still apply. The [downloadable request reference](/api-reference.json) contains the full JSON schemas, including nested fields; it is a request reference, not an OpenAPI response specification. ## Line items Quotes, jobs, invoices, quote templates, and visit line-item replacement accept up to 500 rows. A regular row has `name`, `unitPrice`, and optional `quantity` (default 1), `description`, `unitCost`, `taxable`, and `optional`. `unitCost` must be nonnegative and is used when cost tracking is enabled. `_type` defaults to `lineItem` when omitted. ```json [ {"name":"Window cleaning","quantity":2,"unitPrice":75,"taxable":true}, {"_type":"image","image_path":"workspaces/123/example.png","image_id":456,"description":"Work area"} ] ``` For image rows, first upload a workspace line-item image through [Uploads](/docs/rest-api/uploads), then use its returned storage `path` as `image_path` and upload-link `id` as `image_id`. Do not invent storage paths. Submitting a line-item array replaces that scope's list; retain every row you intend to keep. Job scope locks can reject pricing changes. ## Resources | Resource | Purpose | | ---------------------------------------------------------- | ---------------------------------------------- | | [Workspace](/docs/rest-api/workspace) | Workspace identity, currency, and timezone | | [Contacts, properties, tasks](/docs/rest-api/contacts) | Customers, locations, and follow-ups | | [Leads](/docs/rest-api/leads) | New contact and request together | | [Requests](/docs/rest-api/requests) | Incoming work and conversion | | [Quotes](/docs/rest-api/quotes) | Estimates and approval | | [Quote templates](/docs/rest-api/quote-templates) | Reusable quote content | | [Jobs](/docs/rest-api/jobs) | Work, recurring schedules, holds, and expenses | | [Events](/docs/rest-api/events) | Visits, tasks, and calendar entries | | [Invoices](/docs/rest-api/invoices) | Billing records | | [Payments](/docs/rest-api/payments) | Money received and balances | | [Service plans](/docs/rest-api/service-plans) | Recurring service offerings | | [Subscriptions](/docs/rest-api/service-plan-subscriptions) | Existing enrollments and billing cancellation | | [Company equipment](/docs/rest-api/equipment) | Business vehicles and tools | | [Installed equipment](/docs/rest-api/installed-equipment) | Units at customer properties | | [Timesheets and time entries](/docs/rest-api/timesheets) | Labor and approval | | [Tags](/docs/rest-api/tags) | Labels and assignments | | [Review requests](/docs/rest-api/review-requests) | Customer review invitations | | [Services](/docs/rest-api/services) | Service/product catalog | | [Tax rates](/docs/rest-api/tax-rates) | Named tax rates | | [Users](/docs/rest-api/users) | Workspace members | | [Notifications](/docs/rest-api/notifications) | Customer delivery | | [Comments](/docs/rest-api/comments) | Internal discussion | | [Uploads](/docs/rest-api/uploads) | Attachments and line-item images | # Installed equipment Track units installed at customer properties, such as furnaces and water heaters. A `property_id` is required; the contact is separate and may change while the unit stays in place. Company trucks and tools belong in [Company equipment](/docs/rest-api/equipment). `auto_reminders_enabled` defaults to true. Warranty/replacement dates can therefore schedule customer-facing reminders. Explicitly disable this field if automatic reminders are not wanted. Custom reminders independently select `send_customer` and `send_internal`; their defaults are false and true respectively. The `notifications` REST endpoints correspond to installed-equipment reminder tools in MCP. Notes record service history. Job links connect the unit with past work; removing a link does not delete the job or equipment. Setting `installed_by_job_id` on creation also adds that job link. There are no public update endpoints for installed-equipment notes or reminders. Their supported operations are listed below. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/installed-equipment ```http GET /api/v1/installed-equipment ``` **Required scopes:** `installed_equipment:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | | `contact_id` | string | No | Filter by contact ID. | | `property_id` | string | No | Filter by property ID. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/installed-equipment ```http POST /api/v1/installed-equipment ``` **Required scopes:** `installed_equipment:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------------ | -------------------------------------------------------- | -------- | ------------------------------- | | `name` | string | No | | | `property_id` | integer | Yes | | | `contact_id` | integer or null | No | | | `equipment_type` | string or null | No | e.g. "Furnace", "Water heater". | | `make` | string or null | No | | | `model` | string or null | No | | | `serial_number` | string or null | No | | | `description` | string or null | No | | | `status` | `"active"`, `"needs_service"`, `"replaced"`, `"removed"` | No | Default: `"active"`. | | `install_date` | string or null | No | | | `warranty_expiration` | string or null | No | | | `warranty_number` | string or null | No | | | `replacement_date` | string or null | No | | | `auto_reminders_enabled` | boolean | No | Default: `true`. | | `installed_by_job_id` | integer or null | No | | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/installed-equipment/:id ```http GET /api/v1/installed-equipment/:id ``` **Required scopes:** `installed_equipment: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/installed-equipment/:id ```http PUT /api/v1/installed-equipment/:id ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------------ | -------------------------------------------------------- | -------- | ------------------------------- | | `name` | string | No | | | `property_id` | integer | No | | | `contact_id` | integer or null | No | | | `equipment_type` | string or null | No | e.g. "Furnace", "Water heater". | | `make` | string or null | No | | | `model` | string or null | No | | | `serial_number` | string or null | No | | | `description` | string or null | No | | | `status` | `"active"`, `"needs_service"`, `"replaced"`, `"removed"` | No | Default: `"active"`. | | `install_date` | string or null | No | | | `warranty_expiration` | string or null | No | | | `warranty_number` | string or null | No | | | `replacement_date` | string or null | No | | | `auto_reminders_enabled` | boolean | No | Default: `true`. | | `installed_by_job_id` | integer or null | No | | | `custom_fields` | JSON value or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/installed-equipment/:id ```http DELETE /api/v1/installed-equipment/:id ``` **Required scopes:** `installed_equipment: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](/docs/rest-api#responses-and-errors). ## GET /api/v1/installed-equipment/:id/jobs ```http GET /api/v1/installed-equipment/:id/jobs ``` **Required scopes:** `installed_equipment: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](/docs/rest-api#responses-and-errors). ## POST /api/v1/installed-equipment/:id/jobs ```http POST /api/v1/installed-equipment/:id/jobs ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ---------------------- | -------- | ------- | | `job_id` | integer | Yes | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/installed-equipment/:id/notes ```http GET /api/v1/installed-equipment/:id/notes ``` **Required scopes:** `installed_equipment:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `type` | string | No | Filter by type. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/installed-equipment/:id/notes ```http POST /api/v1/installed-equipment/:id/notes ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------------------- | -------- | ---------------------------------------------------- | | `title` | string | Yes | minLength: 1. | | `content` | string or null | No | | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Default: `"note"`. | | `activity_date` | string or string or null | No | When the work happened, if not when it was recorded. | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/installed-equipment/:id/notifications ```http GET /api/v1/installed-equipment/:id/notifications ``` **Required scopes:** `installed_equipment:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/installed-equipment/:id/notifications ```http POST /api/v1/installed-equipment/:id/notifications ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------- | -------- | ----------------------------------------------------- | | `name` | string | Yes | minLength: 1. | | `subject` | string | Yes | minLength: 1. | | `message` | string | Yes | minLength: 1. | | `remind_on` | string | Yes | Date the reminder fires (YYYY-MM-DD). Format: `date`. | | `reason` | `"warranty"`, `"replacement"`, `"custom"` | No | Default: `"custom"`. | | `send_customer` | boolean | No | Default: `false`. | | `send_internal` | boolean | No | Default: `true`. | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/installed-equipment/:id/jobs/:jobId ```http DELETE /api/v1/installed-equipment/:id/jobs/:jobId ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`, `jobId`. 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/installed-equipment/:id/notes/:noteId ```http DELETE /api/v1/installed-equipment/:id/notes/:noteId ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`, `noteId`. 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/installed-equipment/:id/notifications/:notificationId ```http DELETE /api/v1/installed-equipment/:id/notifications/:notificationId ``` **Required scopes:** `installed_equipment:write`. **Path parameters:** `id`, `notificationId`. 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](/docs/rest-api#responses-and-errors). # Invoices Create and manage one-off invoices. New `type: "recurring"` invoices are rejected; use a [recurring job schedule](/docs/rest-api/jobs) for repeating work. Existing legacy recurring invoices remain editable. ### Sending and payment Setting `status: "sent"` does not email or text the client. Use [Notifications](/docs/rest-api/notifications) with `type: "invoice_to_customer"` for delivery. A scheduled invoice uses `status: "scheduled"` together with `send_on`. To mark an invoice paid, record money received using [Payments](/docs/rest-api/payments). A bare `paid` status write returns `409` while a balance remains. Fully covered and zero-total invoices can be marked paid. The payment settlement flow accounts for completed payments and applicable quote deposits; pending payments block settlement. `GET /invoices/:id` includes payment/balance information. `reminders_enabled` controls reminders, and `waive_credit_card_fee` overrides the workspace's online processing fee. See [line items](/docs/rest-api#line-items). ### Example: record the remaining balance as received ```bash curl 'https://my.bluesuite.app/api/v1/payments' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"object_type":"invoice","object_id":101,"category":"payment","settle_remaining":true}' ``` This records a manual payment and recalculates status. It does not charge a card or send a customer receipt. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/invoices ```http GET /api/v1/invoices ``` **Required scopes:** `invoices:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ------------------ | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `contact_id` | string | No | Filter by contact ID. | | `job_id` | string | No | Filter by job ID. | | `exclude_children` | string | No | Use true to exclude child invoices. | | `exclude_statuses` | string | No | Comma-separated statuses to exclude. | | `tag_ids` | string | No | Comma-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](/docs/rest-api#responses-and-errors). ## POST /api/v1/invoices ```http POST /api/v1/invoices ``` **Required scopes:** `invoices:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | -------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `job_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `date` | string | No | | | `due_date` | string | No | | | `send_on` | string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Default: `"due_on_receipt"`. | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `reminders_enabled` | boolean | No | Default: `true`. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Default: `false`. | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | No | Default: `"draft"`. | | `type` | `"one-off"`, `"recurring"` | No | Default: `"one-off"`. | | `custom_fields` | JSON value or null | No | | | `event_ids` | array of integer | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/invoices/:id ```http GET /api/v1/invoices/:id ``` **Required scopes:** `invoices: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/invoices/:id ```http PUT /api/v1/invoices/:id ``` **Required scopes:** `invoices:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | -------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `job_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `date` | string | No | | | `due_date` | string | No | | | `send_on` | string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Default: `"due_on_receipt"`. | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `reminders_enabled` | boolean | No | Default: `true`. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Default: `false`. | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | No | Default: `"draft"`. | | `type` | `"one-off"`, `"recurring"` | No | Default: `"one-off"`. | | `custom_fields` | JSON value or null | No | | | `event_ids` | array of integer | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/invoices/:id ```http DELETE /api/v1/invoices/:id ``` **Required scopes:** `invoices: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/invoices/:id/status ```http PUT /api/v1/invoices/:id/status ``` **Required scopes:** `invoices:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | -------------------------------------------------------------------------------------------- | -------- | ------- | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | 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](/docs/rest-api#responses-and-errors). # Jobs, schedules, and expenses Jobs group customer work, visits, and billing. See [Events](/docs/rest-api/events) for visits and [Payments](/docs/rest-api/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. ```bash 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](/docs/rest-api/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](/docs/rest-api#line-items) for pricing. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/jobs ```http GET /api/v1/jobs ``` **Required scopes:** `jobs:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `contact_id` | string | No | Filter by contact ID. | | `quote_id` | string | No | Filter by quote ID. | | `status` | string | No | Filter by status. | | `derived_state` | string | No | Comma-separated: missed, overdue, action\_required, needs\_invoicing, unscheduled, awaiting\_payment. Unscheduled matches draft jobs. | | `tag_ids` | string | No | Comma-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](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs ```http POST /api/v1/jobs ``` **Required scopes:** `jobs:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `request_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `status` | `"draft"`, `"scheduled"`, `"in_progress"`, `"completed"`, `"cancelled"` | No | Default: `"draft"`. | | `type` | `"one-off"`, `"recurring"` | No | Default: `"one-off"`. | | `billing_type` | `"per_visit"`, `"manual"` | No | Default: `"per_visit"`. | | `invoice_autopay` | boolean | No | Default: `false`. | | `invoice_payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` or null | No | | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Carries onto invoices created from this job. Default: `false`. | | `expected_end_date` | string or null | No | | | `expenses` | JSON value | No | | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/jobs/:id ```http 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/jobs/:id ```http PUT /api/v1/jobs/:id ``` **Required scopes:** `jobs:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `request_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `type` | `"one-off"`, `"recurring"` | No | Default: `"one-off"`. | | `billing_type` | `"per_visit"`, `"manual"` | No | Default: `"per_visit"`. | | `invoice_autopay` | boolean | No | Default: `false`. | | `invoice_payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` or null | No | | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Carries onto invoices created from this job. Default: `false`. | | `expected_end_date` | string or null | No | | | `expenses` | JSON value | No | | | `custom_fields` | JSON value or null | No | | | `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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/jobs/:id ```http 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/jobs/:id/expenses ```http GET /api/v1/jobs/:id/expenses ``` **Required scopes:** `jobs:read`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs/:id/expenses ```http POST /api/v1/jobs/:id/expenses ``` **Required scopes:** `jobs:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------------- | | `name` | string | Yes | minLength: 1. | | `description` | string or null | No | | | `date` | string or null | No | | | `total` | number | Yes | | | `attachment` | string or null | No | | | `attachments` | array of string | No | | 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](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs/:id/hold ```http 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 | Field | Type / accepted values | Required | Details | | --------------------- | ----------------------------------------------------------------- | -------- | ---------------- | | `reason` | `"customer"`, `"materials"`, `"approval"`, `"weather"`, `"other"` | Yes | | | `notes` | string | No | maxLength: 2000. | | `follow_up_on` | string | No | Format: `date`. | | `cancel_appointments` | boolean | 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](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs/:id/resume ```http 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 | Field | Type / accepted values | Required | Details | | ------------------ | ---------------------- | -------- | -------------------- | | `expected_held_at` | string | Yes | Format: `date-time`. | | `resume_on` | string | No | Format: `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](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs/:id/schedule ```http 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 | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | | `start_date` | string | Yes | First appointment date, YYYY-MM-DD. | | `end_date` | string or null | No | | | `start_time` | string or null | No | | | `end_time` | string or null | No | | | `all_day` | boolean | No | Default: `false`. | | `multi_day` | boolean | No | Default: `false`. | | `is_flexible` | boolean | No | Default: `false`. | | `duration_minutes` | integer or null | No | | | `assignee_ids` | array of string | No | Default: `[]`. | | `billing_type` | `"per_visit"`, `"manual"` | No | Default: `"per_visit"`. | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Default: `"due_on_receipt"`. | | `repeat_frequency` | integer | No | Default: `1`. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"weekly"`. | | `repeat_frequency_schedule` | object or null | No | | | `repeat_frequency_schedule.days` | array of integer | When parent supplied | | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | integer or null | No | | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/jobs/:id/status ```http PUT /api/v1/jobs/:id/status ``` **Required scopes:** `jobs:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ---------------------------------------- | -------- | ------- | | `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](/docs/rest-api#responses-and-errors). ## GET /api/v1/jobs/:id/expenses/:expenseId ```http 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/jobs/:id/expenses/:expenseId ```http 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 | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------------- | | `name` | string | No | minLength: 1. | | `description` | string or null | No | | | `date` | string or null | No | | | `total` | number | No | | | `attachment` | string or null | No | | | `attachments` | array of string | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/jobs/:id/expenses/:expenseId ```http 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](/docs/rest-api#responses-and-errors). ## POST /api/v1/jobs/:id/schedule/end ```http 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 | Field | Type / accepted values | Required | Details | | ---------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | `end_date` | string | Yes | Last 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](/docs/rest-api#responses-and-errors). # Leads Create a contact, optionally create and link a property, then create a request in one call. All three write scopes are required, including `properties:write` when no property is supplied. The response includes `contact_id`, `property_id`, `request_id`, and the created records. The contact defaults to type `lead` unless supplied explicitly. The request's contact and property links are set from the newly created records. This flow does not deduplicate against existing contacts and is not a database transaction; inspect the workspace before retrying a failed request. For an existing customer, use the individual contact/property/request endpoints. ### Example: capture an inquiry ```bash curl 'https://my.bluesuite.app/api/v1/leads' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"contact":{"first_name":"Jamie","last_name":"Rivera","email":"jamie@example.com"},"request":{"name":"Annual HVAC maintenance","details":"Please contact me to schedule."}}' ``` {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## POST /api/v1/leads ```http POST /api/v1/leads ``` **Required scopes:** `contacts:write`, `properties:write`, `requests:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------------------ | --------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------- | | `contact` | object | Yes | | | `contact.first_name` | string | No | Default: `""`. | | `contact.last_name` | string | No | Default: `""`. | | `contact.email` | string or null | No | | | `contact.phone` | string or null | No | | | `contact.secondary_email` | string or null | No | | | `contact.secondary_phone` | string or null | No | | | `contact.company` | string or null | No | | | `contact.company_department` | string or null | No | | | `contact.company_position` | string or null | No | | | `contact.title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` or null | No | | | `contact.type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Default: `"none"`. | | `contact.owner_id` | string or null | No | | | `contact.info` | JSON value or null | No | | | `contact.custom_fields` | JSON value or null | No | | | `property` | object | No | | | `property.address` | object | When parent supplied | | | `property.address.street` | string | No | Default: `""`. | | `property.address.street_2` | string | No | Default: `""`. | | `property.address.city` | string | No | Default: `""`. | | `property.address.state` | string | No | Default: `""`. | | `property.address.postal_code` | string | No | Default: `""`. | | `property.address.country` | string | No | Default: `""`. | | `property.property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Default: `"single-family"`. | | `property.role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | No | Default: `"owner"`. | | `request` | object | Yes | | | `request.name` | string or null | No | | | `request.contact_id` | integer or null | No | | | `request.property_id` | integer or null | No | | | `request.assignee_id` | string or null | No | | | `request.details` | string or null | No | | | `request.assessment` | string or null | No | | | `request.requested_on` | string | No | Pattern: `^\d{4}-\d{2}-\d{2}$`. | | `request.status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Default: `"new"`. | | `request.data` | JSON value or null | No | | | `request.custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). # Notifications Send quotes, invoices, or appointment reminders using the workspace's customer-notification channels. Changing a record's status is not delivery. | Type | Parent | Required API-key scope | | ---------------------- | ---------- | ---------------------- | | `quote_to_customer` | Quote ID | `notifications:write` | | `invoice_to_customer` | Invoice ID | `notifications:write` | | `appointment_reminder` | Event ID | `notifications:write` | Initial quote/invoice sends transition status to awaiting response/sent. `reminder: true` sends a reminder without performing that initial-send transition. Delivery follows email/SMS workspace settings and contact SMS consent. The current endpoint requires an associated contact with an email address. Appointment reminders require an appointment linked to a contact through a job, request, or subscription; they target the customer rather than the assigned technicians. ### Example: deliver an invoice ```bash curl 'https://my.bluesuite.app/api/v1/notifications' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"type":"invoice_to_customer","module_id":101}' ``` The response data contains `queued: true`, `type`, `module`, `module_id`, and `recipient`. It acknowledges the notification operation, not a delivery/read receipt from the recipient. A status change alone is not proof of delivery. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## POST /api/v1/notifications ```http POST /api/v1/notifications ``` **Required scopes:** `notifications:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ----------- | ------------------------------------------------------------------------ | -------- | ----------------- | | `type` | `"quote_to_customer"`, `"invoice_to_customer"`, `"appointment_reminder"` | Yes | | | `module_id` | integer | Yes | | | `reminder` | boolean | No | Default: `false`. | 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](/docs/rest-api#responses-and-errors). # Payments Record and maintain payments against invoices or quotes. These endpoints use `invoices:read` and `invoices:write` scopes. Recording a manual payment does not charge a card. Payment changes recalculate the parent balance/status. ### Settle an invoice Send `object_type: "invoice"`, its `object_id`, `category: "payment"`, and `settle_remaining: true`, omitting `amount`. The server calculates the remaining balance and records a completed manual payment. The default method is Other; a supplied method and `created_at` are preserved. Pending payments block settlement. For an explicit amount, omit `settle_remaining` and provide `amount`. Tips must be nonnegative. A timestamp, when supplied, must include a timezone. Settlement is for recording funds already received, not collecting them or sending a receipt. Deleting or editing a payment changes the accounting record; it is not a Stripe refund operation. Inspect the returned record and parent invoice after a change. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/payments ```http GET /api/v1/payments ``` **Required scopes:** `invoices:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `object_id` | string | No | ID of the invoice or quote. | | `object_type` | string | No | invoice or quote. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/payments ```http POST /api/v1/payments ``` **Required scopes:** `invoices:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------ | ------------------------------------------------------------------------------------------ | -------- | ------------------------- | | `object_id` | integer | Yes | | | `object_type` | `"invoice"`, `"quote"` | Yes | | | `category` | `"deposit"`, `"payment"` | Yes | | | `amount` | number | No | | | `tip_amount` | number | No | Default: `0`. minimum: 0. | | `status` | `"pending"`, `"completed"`, `"failed"`, `"refunded"` | No | Default: `"completed"`. | | `gateway` | `"manual"`, `"stripe:connect"` | No | Default: `"manual"`. | | `method` | `"other"`, `"credit_card"`, `"cash"`, `"check"`, `"bank_transfer"`, `"ach"`, `"financing"` | No | Default: `"other"`. | | `notes` | string or null | No | | | `created_at` | string | No | Format: `date-time`. | | `settle_remaining` | boolean | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/payments/:id ```http GET /api/v1/payments/:id ``` **Required scopes:** `invoices: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/payments/:id ```http PUT /api/v1/payments/:id ``` **Required scopes:** `invoices:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ------------------------------------------------------------------------------------------ | -------- | ------------------------- | | `object_id` | integer | No | | | `object_type` | `"invoice"`, `"quote"` | No | | | `category` | `"deposit"`, `"payment"` | No | | | `amount` | number | No | | | `tip_amount` | number | No | Default: `0`. minimum: 0. | | `status` | `"pending"`, `"completed"`, `"failed"`, `"refunded"` | No | Default: `"completed"`. | | `gateway` | `"manual"`, `"stripe:connect"` | No | Default: `"manual"`. | | `method` | `"other"`, `"credit_card"`, `"cash"`, `"check"`, `"bank_transfer"`, `"ach"`, `"financing"` | No | Default: `"other"`. | | `notes` | string or null | No | | | `created_at` | string | No | Format: `date-time`. | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/payments/:id ```http DELETE /api/v1/payments/:id ``` **Required scopes:** `invoices: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](/docs/rest-api#responses-and-errors). # Quote templates Save reusable quote content and create quotes from it. `POST /quote-templates/:id/create-quote` accepts optional overrides, including the customer and property. An empty JSON object uses the template's defaults. The result is a created quote; sending it is a separate [notification](/docs/rest-api/notifications). Use [line items](/docs/rest-api#line-items) for pricing and service details. The template and any associated IDs must belong to the authenticated workspace. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/quote-templates ```http GET /api/v1/quote-templates ``` **Required scopes:** `quote-templates:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/quote-templates ```http POST /api/v1/quote-templates ``` **Required scopes:** `quote-templates:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- | | `template_name` | string | Yes | | | `name` | string or null | No | | | `description` | string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/quote-templates/:id ```http GET /api/v1/quote-templates/:id ``` **Required scopes:** `quote-templates: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/quote-templates/:id ```http PUT /api/v1/quote-templates/:id ``` **Required scopes:** `quote-templates:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- | | `template_name` | string | No | | | `name` | string or null | No | | | `description` | string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/quote-templates/:id ```http DELETE /api/v1/quote-templates/:id ``` **Required scopes:** `quote-templates: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](/docs/rest-api#responses-and-errors). ## POST /api/v1/quote-templates/:id/create-quote ```http POST /api/v1/quote-templates/:id/create-quote ``` **Required scopes:** `quotes:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------ | -------- | -------------------------------------------- | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `name` | string or null | No | Overrides the name copied from the template. | | `salesperson_id` | string or null | No | | | `expire_on` | string or string or null | No | | 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](/docs/rest-api#responses-and-errors). # Quotes Create drafts, edit pricing, and track quote status. Creating a quote or setting its status does not deliver it to the customer. Use [Notifications](/docs/rest-api/notifications) with `type: "quote_to_customer"` and the quote ID to send it using the workspace's email/SMS settings. `reminders_enabled` controls reminders. `waive_credit_card_fee` overrides the workspace's online processing fee for this record. Record deposits through [Payments](/docs/rest-api/payments). See [line items](/docs/rest-api#line-items) for the shared pricing structure. List filters `status` and `exclude_statuses` accept comma-separated values; `tag_ids` matches any selected tag. ### Example: create a draft ```bash curl 'https://my.bluesuite.app/api/v1/quotes' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"contact_id":42,"name":"Window cleaning","status":"draft","line_items":[{"name":"Window cleaning","quantity":1,"unitPrice":150}],"total":150}' ``` {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/quotes ```http GET /api/v1/quotes ``` **Required scopes:** `quotes:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ------------------ | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `contact_id` | string | No | Filter by contact ID. | | `exclude_statuses` | string | No | Comma-separated statuses to exclude. | | `status` | string | No | Filter by status. | | `tag_ids` | string | No | Comma-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](/docs/rest-api#responses-and-errors). ## POST /api/v1/quotes ```http POST /api/v1/quotes ``` **Required scopes:** `quotes:write`. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `request_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `template_id` | integer or null | No | | | `date` | string | No | | | `expire_on` | string or string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `reminders_enabled` | boolean | No | Default: `true`. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Carries over to jobs and invoices created from it. Default: `false`. | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Default: `"draft"`. | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/quotes/:id ```http GET /api/v1/quotes/:id ``` **Required scopes:** `quotes: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/quotes/:id ```http PUT /api/v1/quotes/:id ``` **Required scopes:** `quotes:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `request_id` | integer or null | No | | | `salesperson_id` | string or null | No | | | `template_id` | integer or null | No | | | `date` | string | No | | | `expire_on` | string or string or null | No | | | `message` | string or null | No | | | `payment_instructions` | string or null | No | | | `line_items` | array of object or object | No | maxItems: 500. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string or null | No | For lineItem rows. Additional description | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unitPrice` | number | For this variant | For lineItem rows. Unit price | | `line_items[].unitCost` | number | No | For lineItem rows. What this costs the business, per unit. Only used when cost tracking is enabled. minimum: 0. | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by /uploads minLength: 1. | | `line_items[].image_id` | integer or null | No | For image rows. Upload link ID returned by /uploads | | `line_items[].description` | string or null | No | For image rows. Caption shown beneath the image | | `discount` | number | No | Default: `0`. | | `discount_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `deposit` | number | No | Default: `0`. | | `deposit_unit` | `"%"`, `"$"` | No | Default: `"%"`. | | `tax_rate` | number | No | Default: `0`. | | `tax_rate_id` | integer or null | No | | | `total` | number | No | Default: `0`. | | `reminders_enabled` | boolean | No | Default: `true`. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Carries over to jobs and invoices created from it. Default: `false`. | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Default: `"draft"`. | | `custom_fields` | JSON value or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/quotes/:id ```http DELETE /api/v1/quotes/:id ``` **Required scopes:** `quotes: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/quotes/:id/status ```http PUT /api/v1/quotes/:id/status ``` **Required scopes:** `quotes:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------- | ------------------------------------------------------------------------------------------------------------- | -------- | ------- | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | Yes | | | `comment` | string | 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](/docs/rest-api#responses-and-errors). # Requests Create and manage requests from existing contacts. For a new inquiry that also needs a contact and property, use [Leads](/docs/rest-api/leads). `POST /requests/:id/convert` creates a draft job, quote, or invoice carrying the request's contact and property, then marks the request converted. The result contains `converted_to`, the new record's `id`, and `record`. It does not send a quote or invoice to the customer. The invoice table does not retain a request ID link. The list filter `status` accepts comma-separated values. `tag_ids` matches any selected tag. ### Example: convert to a quote ```bash curl 'https://my.bluesuite.app/api/v1/requests/42/convert' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"to":"quote","name":"Spring maintenance"}' ``` {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/requests ```http GET /api/v1/requests ``` **Required scopes:** `requests:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `contact_id` | string | No | Filter by contact ID. | | `status` | string | No | Filter by status. | | `tag_ids` | string | No | Comma-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](/docs/rest-api#responses-and-errors). ## POST /api/v1/requests ```http POST /api/v1/requests ``` **Required scopes:** `requests:write`. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `assignee_id` | string or null | No | | | `details` | string or null | No | | | `assessment` | string or null | No | | | `requested_on` | string | No | Pattern: `^\d{4}-\d{2}-\d{2}$`. | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Default: `"new"`. | | `data` | JSON value or null | No | | | `custom_fields` | JSON value or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/requests/:id ```http GET /api/v1/requests/:id ``` **Required scopes:** `requests: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/requests/:id ```http PUT /api/v1/requests/:id ``` **Required scopes:** `requests:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | ------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------- | | `name` | string or null | No | | | `contact_id` | integer or null | No | | | `property_id` | integer or null | No | | | `assignee_id` | string or null | No | | | `details` | string or null | No | | | `assessment` | string or null | No | | | `requested_on` | string | No | Pattern: `^\d{4}-\d{2}-\d{2}$`. | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Default: `"new"`. | | `data` | JSON value or null | No | | | `custom_fields` | JSON value or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/requests/:id ```http DELETE /api/v1/requests/:id ``` **Required scopes:** `requests: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](/docs/rest-api#responses-and-errors). ## POST /api/v1/requests/:id/convert ```http POST /api/v1/requests/:id/convert ``` **Required scopes:** `requests:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------ | ------------------------------- | -------- | -------------------------------------------------------------------- | | `to` | `"job"`, `"quote"`, `"invoice"` | Yes | What to create from this request. | | `name` | string or null | No | Name for the new record. Defaults to the request name plus a suffix. | 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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/requests/:id/status ```http PUT /api/v1/requests/:id/status ``` **Required scopes:** `requests:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------------------------------------------------ | -------- | ------- | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | 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](/docs/rest-api#responses-and-errors). # Review requests Send a review request immediately or list prior requests and their responses. Sending requires the reviews module, configured review platform links, and at least one reachable channel enabled in workspace settings. SMS requires the contact's consent. The workspace cooldown and prior-review suppression apply unless `ignore_cooldown: true` is explicitly supplied. Suppression returns `409`. Supplying an `event_id` selects the appointment template. A successful send returns `id`, `contact_id`, `delivered_via`, `skipped_channels`, `review_page_url`, and `expires_at`. This operation communicates with the customer immediately. A request that cannot be delivered returns an error instead of reporting it as sent. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/review-requests ```http GET /api/v1/review-requests ``` **Required scopes:** `reviews:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `contact_id` | string | No | Filter by contact ID. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/review-requests ```http POST /api/v1/review-requests ``` **Required scopes:** `reviews:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ----------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact_id` | integer | Yes | Client to ask. They need an email address, or a mobile with SMS consent. | | `event_id` | integer | No | The appointment this is about. Including it uses the appointment template, which names the visit and the crew who attended. | | `invoice_id` | integer | No | The invoice this is about, when the request follows a payment. | | `ignore_cooldown` | boolean | No | Ask even if this client was asked recently or has already left a review. Off by default, the workspace's cooldown normally applies. Only set this when the user has said to ask anyway. | 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](/docs/rest-api#responses-and-errors). # Service-plan subscriptions Read existing customer subscriptions, change their contact/property/account manager, or cancel them. Public v1 does not provide subscription creation, pricing edits, or a general subscription status setter. Enrollment uses the customer portal or dashboard flow. ### Cancellation and billing Use `POST /service-plan-subscriptions/:id/cancel`. For Stripe-backed subscriptions, the endpoint cancels Stripe billing before marking the BlueSuite subscription cancelled. A missing connection returns `409`; a failed Stripe cancellation returns `502`, leaving the subscription active. `force: true` permits a BlueSuite-only cancellation when billing cannot be stopped. It can leave Stripe charging the customer. Only use it when that consequence is intended. Check `billing_stopped` and `stripe_cancelled` in the result. Already-cancelled subscriptions return `already_cancelled: true`. An empty JSON object uses the default `force: false`. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/service-plan-subscriptions ```http GET /api/v1/service-plan-subscriptions ``` **Required scopes:** `service_plans:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ----------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | | `service_plan_id` | string | No | Filter by service plan ID. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## GET /api/v1/service-plan-subscriptions/:id ```http GET /api/v1/service-plan-subscriptions/:id ``` **Required scopes:** `service_plans: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/service-plan-subscriptions/:id ```http PUT /api/v1/service-plan-subscriptions/:id ``` **Required scopes:** `service_plans:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------------------- | ---------------------- | -------- | ------- | | `contact_id` | integer | No | | | `property_id` | integer or null | No | | | `account_manager_id` | string or null | 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](/docs/rest-api#responses-and-errors). ## POST /api/v1/service-plan-subscriptions/:id/cancel ```http POST /api/v1/service-plan-subscriptions/:id/cancel ``` **Required scopes:** `service_plans:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------- | ---------------------- | -------- | ----------------- | | `force` | boolean | No | Default: `false`. | 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](/docs/rest-api#responses-and-errors). # Service plans A service plan defines recurring billing and optional visits. Creating or editing a plan does not subscribe or charge a customer. Billing cadence and visit cadence are independent. Use `no_appointments: true` for billing-only plans. A `fixed_end_date` duration requires `duration_end_date`, including when updating or activating an existing plan. Status is `draft`, `active`, or `archived`. For existing customers enrolled in a plan, use [Service-plan subscriptions](/docs/rest-api/service-plan-subscriptions). {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/service-plans ```http GET /api/v1/service-plans ``` **Required scopes:** `service_plans:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/service-plans ```http POST /api/v1/service-plans ``` **Required scopes:** `service_plans:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ---------------------------- | ---------------------------------------------------------------------------------------- | -------- | ----------------------------- | | `name` | string | Yes | minLength: 1. | | `description` | string or null | No | | | `billing_amount` | number | No | Default: `0`. minimum: 0. | | `billing_cycle` | `"weekly"`, `"monthly"`, `"quarterly"`, `"semi_annually"`, `"annually"` | No | Default: `"monthly"`. | | `billing_timing` | `"start"`, `"end"` | No | Default: `"start"`. | | `duration_type` | `"until_cancelled"`, `"fixed_end_date"` | No | Default: `"until_cancelled"`. | | `duration_end_date` | string or null | No | | | `visit_recurrence_frequency` | integer | No | Default: `1`. | | `visit_recurrence_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"monthly"`. | | `visit_recurrence_schedule` | JSON value or null | No | | | `no_appointments` | boolean | No | Default: `false`. | | `tax_rate` | number | No | Default: `0`. minimum: 0. | | `tax_rate_id` | integer or null | No | | | `terms` | string or null | No | | | `default_account_manager_id` | string or null | No | | | `addons` | JSON value or null | No | | | `status` | `"draft"`, `"active"`, `"archived"` | No | Default: `"draft"`. | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/service-plans/:id ```http GET /api/v1/service-plans/:id ``` **Required scopes:** `service_plans: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/service-plans/:id ```http PUT /api/v1/service-plans/:id ``` **Required scopes:** `service_plans:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ---------------------------- | ---------------------------------------------------------------------------------------- | -------- | ----------------------------- | | `name` | string | No | minLength: 1. | | `description` | string or null | No | | | `billing_amount` | number | No | Default: `0`. minimum: 0. | | `billing_cycle` | `"weekly"`, `"monthly"`, `"quarterly"`, `"semi_annually"`, `"annually"` | No | Default: `"monthly"`. | | `billing_timing` | `"start"`, `"end"` | No | Default: `"start"`. | | `duration_type` | `"until_cancelled"`, `"fixed_end_date"` | No | Default: `"until_cancelled"`. | | `duration_end_date` | string or null | No | | | `visit_recurrence_frequency` | integer | No | Default: `1`. | | `visit_recurrence_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Default: `"monthly"`. | | `visit_recurrence_schedule` | JSON value or null | No | | | `no_appointments` | boolean | No | Default: `false`. | | `tax_rate` | number | No | Default: `0`. minimum: 0. | | `tax_rate_id` | integer or null | No | | | `terms` | string or null | No | | | `default_account_manager_id` | string or null | No | | | `addons` | JSON value or null | No | | | `status` | `"draft"`, `"active"`, `"archived"` | No | Default: `"draft"`. | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/service-plans/:id ```http DELETE /api/v1/service-plans/:id ``` **Required scopes:** `service_plans: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/service-plans/:id/status ```http PUT /api/v1/service-plans/:id/status ``` **Required scopes:** `service_plans:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | ----------------------------------- | -------- | ------- | | `status` | `"draft"`, `"active"`, `"archived"` | 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](/docs/rest-api#responses-and-errors). # Services Maintain the service/product catalog used in quote, job, and invoice line items. List services with search/pagination or `id_in`, a comma-separated list of service IDs. Creating a catalog service does not add it to existing quotes or jobs. See [line items](/docs/rest-api#line-items) for how quantities, prices, and catalog references are used in documents. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/services ```http GET /api/v1/services ``` **Required scopes:** `services:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `id_in` | string | No | Comma-separated record IDs. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/services ```http POST /api/v1/services ``` **Required scopes:** `services:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ------------------------ | -------- | --------------------- | | `name` | string | Yes | minLength: 1. | | `description` | string or null | No | | | `price` | number | No | Default: `0`. | | `sku` | string or null | No | | | `type` | `"service"`, `"product"` | No | Default: `"service"`. | | `taxable` | boolean | No | Default: `true`. | | `image_path` | string or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/services/:id ```http GET /api/v1/services/:id ``` **Required scopes:** `services: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/services/:id ```http PUT /api/v1/services/:id ``` **Required scopes:** `services:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ------------------------ | -------- | --------------------- | | `name` | string | No | minLength: 1. | | `description` | string or null | No | | | `price` | number | No | Default: `0`. | | `sku` | string or null | No | | | `type` | `"service"`, `"product"` | No | Default: `"service"`. | | `taxable` | boolean | No | Default: `true`. | | `image_path` | string or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/services/:id ```http DELETE /api/v1/services/:id ``` **Required scopes:** `services: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](/docs/rest-api#responses-and-errors). # Tags Tags belong to one entity type: contact, request, quote, job, or invoice. The same label on two entity types represents separate tags. A tag's type cannot be changed by the update endpoint. `GET /tags` requires `taggable_type`. Assignment reads require that type plus `taggable_id`. Assignment writes replace the entity's full tag set; an empty `tag_ids` array clears it. To add a tag while keeping existing ones, read the current set and include all desired IDs in the update. `GET` and `PUT /tags/assignments` return an array in `data`, without pagination. Contact/request/quote/job/invoice lists also accept comma-separated `tag_ids`, matching any of the selected tags. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/tags ```http GET /api/v1/tags ``` **Required scopes:** `tags:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `taggable_type` | string | Yes | One of request, quote, job, invoice, contact. Required. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/tags ```http POST /api/v1/tags ``` **Required scopes:** `tags:write`. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | -------------- | | `name` | string | Yes | minLength: 1. | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | | | `description` | string | No | Default: `""`. | | `color` | string or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/tags/:id ```http GET /api/v1/tags/:id ``` **Required scopes:** `tags: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/tags/:id ```http PUT /api/v1/tags/:id ``` **Required scopes:** `tags:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | -------------- | | `name` | string | No | minLength: 1. | | `description` | string | No | Default: `""`. | | `color` | string or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/tags/:id ```http DELETE /api/v1/tags/:id ``` **Required scopes:** `tags: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](/docs/rest-api#responses-and-errors). ## GET /api/v1/tags/assignments ```http GET /api/v1/tags/assignments ``` **Required scopes:** `tags:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | --------------- | ---------------------- | -------- | ------------------------------------------------------- | | `taggable_type` | string | Yes | One of request, quote, job, invoice, contact. Required. | | `taggable_id` | string | Yes | Positive integer ID of the tagged record. Required. | ### Response `200` with `{ "success": true, "data": ... }`. The data contains the result of the operation. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## PUT /api/v1/tags/assignments ```http PUT /api/v1/tags/assignments ``` **Required scopes:** `tags:write`. ### JSON body | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | ------- | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | | | `taggable_id` | integer | Yes | | | `tag_ids` | array of integer | 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](/docs/rest-api#responses-and-errors). # Tax rates Maintain named tax rates for the workspace. Use the returned tax-rate ID when associating a rate with a quote, job, invoice, or service plan. Percentage values are expressed as numbers; for example, 8.5 represents 8.5 percent. Creating or editing a tax-rate record does not by itself rewrite every existing document's pricing. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/tax-rates ```http GET /api/v1/tax-rates ``` **Required scopes:** `tax-rates:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/tax-rates ```http POST /api/v1/tax-rates ``` **Required scopes:** `tax-rates:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------- | | `name` | string | Yes | | | `description` | string | No | | | `rate` | number | Yes | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/tax-rates/:id ```http GET /api/v1/tax-rates/:id ``` **Required scopes:** `tax-rates: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/tax-rates/:id ```http PUT /api/v1/tax-rates/:id ``` **Required scopes:** `tax-rates:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------- | | `name` | string | No | | | `description` | string | No | | | `rate` | number | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/tax-rates/:id ```http DELETE /api/v1/tax-rates/:id ``` **Required scopes:** `tax-rates: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](/docs/rest-api#responses-and-errors). # Timesheets and time entries List timesheets, inspect their entries, and update approval status. There is no timesheet creation endpoint; create time entries instead. Both resources use `timesheets:read` / `timesheets:write` scopes. Time entries can use explicit start/end times or `total_time` without invented clock times. `total_time` is an integer number of **minutes**, from 0 to 1,440. Dates must be real calendar dates; time inputs must be valid clock times. The create route defaults the user to the authenticated acting user when `user_id` is omitted. Resolve other users through [Users](/docs/rest-api/users). An explicit `hourly_rate` override requires a manager or higher; omitting it lets the stored labor rate apply, while null leaves the entry uncosted. Associate an entry with a job, request, quote, or service-plan subscription as supported by the fields below. Do not supply conflicting parent associations. Updates preserve omitted fields and recalculate duration when timing changes. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/time-entries ```http GET /api/v1/time-entries ``` **Required scopes:** `timesheets:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ------------------------------ | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `start_date` | string | No | Inclusive start date, YYYY-MM-DD. | | `end_date` | string | No | Inclusive end date, YYYY-MM-DD. | | `user_id` | string | No | Filter by user UUID from GET /users. | | `job_id` | string | No | Filter by job ID. | | `request_id` | string | No | Filter by request ID. | | `quote_id` | string | No | Filter by quote ID. | | `service_plan_subscription_id` | string | No | Filter by subscription ID. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/time-entries ```http POST /api/v1/time-entries ``` **Required scopes:** `timesheets:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------------------ | ---------------------- | -------- | -------------------------- | | `start_date` | string | Yes | Format: `date`. | | `end_date` | string | Yes | Format: `date`. | | `start_time` | string or null | No | | | `end_time` | string or null | No | | | `total_time` | integer | No | minimum: 0. maximum: 1440. | | `user_id` | string or null | No | | | `hourly_rate` | number or null | No | | | `description` | string or null | No | | | `job_id` | integer or null | No | | | `request_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `service_plan_subscription_id` | integer or null | No | | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/timesheets ```http GET /api/v1/timesheets ``` **Required scopes:** `timesheets:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | | `start_date` | string | No | Inclusive start date, YYYY-MM-DD. | | `end_date` | string | No | Inclusive end date, YYYY-MM-DD. | | `user_id` | string | No | Filter by user UUID from GET /users. | | `status` | string | No | Filter by status. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## PUT /api/v1/time-entries/:id ```http PUT /api/v1/time-entries/:id ``` **Required scopes:** `timesheets:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------------------------ | ---------------------- | -------- | -------------------------- | | `start_date` | string | No | Format: `date`. | | `end_date` | string | No | Format: `date`. | | `start_time` | string or null | No | | | `end_time` | string or null | No | | | `total_time` | integer | No | minimum: 0. maximum: 1440. | | `user_id` | string or null | No | | | `hourly_rate` | number or null | No | | | `description` | string or null | No | | | `job_id` | integer or null | No | | | `request_id` | integer or null | No | | | `quote_id` | integer or null | No | | | `service_plan_subscription_id` | integer or null | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/time-entries/:id ```http DELETE /api/v1/time-entries/:id ``` **Required scopes:** `timesheets: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](/docs/rest-api#responses-and-errors). ## GET /api/v1/timesheets/:id ```http GET /api/v1/timesheets/:id ``` **Required scopes:** `timesheets: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](/docs/rest-api#responses-and-errors). ## PUT /api/v1/timesheets/:id/status ```http PUT /api/v1/timesheets/:id/status ``` **Required scopes:** `timesheets:write`. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | -------- | -------------------------------------------------- | -------- | ------- | | `status` | `"draft"`, `"pending"`, `"approved"`, `"rejected"` | 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](/docs/rest-api#responses-and-errors). # Files and uploads Attach files to contacts, requests, quotes, jobs, invoices, events, or timesheets. Workspace-level uploads are reserved for line-item images. ### Permissions and visibility Every upload operation requires `files:read` or `files:write`, plus the corresponding parent read/write scope. For example, attaching a job receipt needs `files:write` and `jobs:write`. Workspace line-item images require only the files scope, and `module_id` must equal the authenticated workspace ID. `module_type: "workspace"` requires `category: "line_item"`, and that category requires workspace ownership. Other categories include attachment, internal, public, and expense. Choose `is_public` deliberately; its default is false. Upload using multipart form data. The binary file is separate from the fields in the table. Metadata updates use **PATCH**, and require at least one editable field. This API does not move a file to another parent or replace its binary content. ### Example: attach a receipt ```bash curl 'https://my.bluesuite.app/api/v1/uploads' \ -H "Authorization: Bearer $BLUESUITE_API_KEY" \ -F 'file=@receipt.pdf' \ -F 'module_type=job' -F 'module_id=42' -F 'category=expense' ``` For AI-host attachments and file URLs, see the [MCP upload tool](/docs/mcp/tools/uploads). ### Current listing limit The list response includes pagination metadata, but the current query validator accepts only `module_type`, `module_id`, and `category`. Supplying `page` or `per_page` is rejected before pagination is applied, so this route currently exposes the first 25 results only. This also affects MCP `list_files` calls that supply pagination arguments. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/uploads ```http GET /api/v1/uploads ``` **Required scopes:** Determined by the parent entity; see the permissions above.. ### Query parameters | Field | Type / accepted values | Required | Details | | ------------- | -------------------------------------------------------------------------------------------------- | -------- | ------------------- | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"`, `"workspace"` | Yes | Parent entity type. | | `module_id` | integer | Yes | Parent entity ID. | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | No | | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/uploads ```http POST /api/v1/uploads ``` **Required scopes:** Determined by the parent entity; see the permissions above.. ### Multipart form fields Send `multipart/form-data` with a binary `file` part and the fields below. Let your HTTP client set the boundary. | Field | Type / accepted values | Required | Details | | ------------- | -------------------------------------------------------------------------------------------------- | -------- | ------------------------------- | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"`, `"workspace"` | Yes | | | `module_id` | integer | Yes | | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | No | Default: `"attachment"`. | | `name` | string | No | maxLength: 255. | | `description` | string | No | Default: `""`. maxLength: 2000. | | `is_public` | boolean or `"true"`, `"false"` | No | Default: `false`. | 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](/docs/rest-api#responses-and-errors). ## GET /api/v1/uploads/:id ```http GET /api/v1/uploads/:id ``` **Required scopes:** Determined by the parent entity; see the permissions above.. **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](/docs/rest-api#responses-and-errors). ## PATCH /api/v1/uploads/:id ```http PATCH /api/v1/uploads/:id ``` **Required scopes:** Determined by the parent entity; see the permissions above.. **Path parameters:** `id`. Use positive integer record IDs from this workspace. ### JSON body | Field | Type / accepted values | Required | Details | | ------------- | -------------------------------------------------------------------- | -------- | ----------------------------- | | `name` | string | No | minLength: 1. maxLength: 255. | | `description` | string or null | No | | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | 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](/docs/rest-api#responses-and-errors). ## DELETE /api/v1/uploads/:id ```http DELETE /api/v1/uploads/:id ``` **Required scopes:** Determined by the parent entity; see the permissions above.. **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](/docs/rest-api#responses-and-errors). # Users List workspace users to resolve a name or email to the UUID used in owner, salesperson, account-manager, and assignee fields. Use the returned `id` / `user_id`, rather than an email address, in UUID fields. The REST API also supports adding a user and editing membership/profile fields. `POST /users` returns an existing membership when the email is already in the workspace, adds an existing account when it is not a member, or invokes the invitation flow for a new account. These operations can affect workspace access. `PATCH /users` updates the specified user's profile name and/or workspace role and active status. Its response is `{ "data": ... }` without the usual `success` flag. MCP exposes only `list_users`; it does not expose these user writes. For listing, supported sort columns are `first_name`, `last_name`, `email`, `role`, `job_title`, `created_at`, and `updated_at`. `name` is an alias for `first_name`. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/users ```http GET /api/v1/users ``` **Required scopes:** `users:read`. ### Query parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------- | | `page` | integer | No | Page number, starting at 1. Default: `1`. | | `per_page` | integer | No | Records per page, from 1 to 100. Default: `25`. minimum: 1. maximum: 100. | | `search` | string | No | Search text. maxLength: 100. | | `sort_by` | string | No | Sort column. See the supported columns below when listed. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction. Default: `"desc"`. | ### Response `200` with `{ "success": true, "data": [...], "pagination": { "page": 1, "per_page": 25, "total_pages": 1 } }`. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## POST /api/v1/users ```http POST /api/v1/users ``` **Required scopes:** `users:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ------------------------------------------- | | `email` | string | Yes | Email of the user to add to this workspace. | | `first_name` | string | No | | | `last_name` | string | No | | | `role` | string | No | Workspace role. Default: `"member"`. | ### Response `201` with `{ "success": true, "data": ... }`. The data contains the result of the operation. See [response conventions and errors](/docs/rest-api#responses-and-errors). ## PATCH /api/v1/users ```http PATCH /api/v1/users ``` **Required scopes:** `users:write`. ### JSON body | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ------------------------------------ | | `user_id` | string | Yes | Profile UUID returned by GET /users. | | `first_name` | string | No | | | `last_name` | string | No | | | `role` | string | No | Workspace role. | | `is_active` | boolean | No | | ### Response `200` with `{ "data": { "user_id": "...", ...updatedFields } }`. This route omits the usual `success` flag. See [response conventions and errors](/docs/rest-api#responses-and-errors). # Workspace Read the workspace associated with your credentials. The response includes `id`, `name`, `slug`, `industry`, `industry_key`, `country`, `currency`, `timezone`, and `about`. It does not expose the complete settings or feature-permission object. Use the timezone before interpreting local dates. This endpoint does not update workspace settings. {/* Generated reference below. Run npm run docs:generate from the repository root. */} ## GET /api/v1/workspace ```http GET /api/v1/workspace ``` **Required scopes:** `workspace:read`. ### Response `200` with `{ "success": true, "data": ... }`. The data contains the result of the operation. See [response conventions and errors](/docs/rest-api#responses-and-errors). # Comments See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/comments). ## create\_comment Post an internal comment on a parent entity (contact, request, quote, job, invoice, event, or timesheet). Comments are not customer-facing, they show up in the dashboard's activity panel for the parent. Optionally @-mention workspace members (each gets a comments\_mention notification) and/or reply to an existing comment (the original author gets a comments\_reply notification). For quote/job/invoice/request comments, the parent entity's creator also receives a comments\_new notification (skipped if they're already covered by mention/reply dedup or if they wrote this comment). The comment author is the user who created the API key. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"` | Yes | Parent entity type. | | `module_id` | integer | Yes | ID of the parent entity in the current workspace. | | `comment` | string | Yes | Comment body (1 to 10,000 characters). minLength: 1. maxLength: 10000. | | `mentioned_user_ids` | array of string | No | UUIDs of workspace members to @-mention. Each receives a comments\_mention notification. Use list\_users to resolve names to UUIDs. maxItems: 50. | | `reply_to` | integer | No | ID of an existing comment to reply to. The parent comment's author receives a comments\_reply notification. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_comments List internal comments attached to a parent entity (contact, request, quote, job, invoice, event, or timesheet). Returns paginated comments newest-first; soft-deleted comments are excluded. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"` | Yes | Parent entity type. | | `module_id` | integer | Yes | Parent entity ID. | | `page` | JSON value | No | Page number (default: 1). | | `per_page` | integer | No | Results per page (default: 25, max 100). minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_comment Get a single comment by ID, including the joined author profile. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ----------- | | `comment_id` | integer | Yes | Comment ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_comment Soft-delete a comment. The row is retained for audit but stops appearing in list responses. Cannot be undone via this API. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ----------- | | `comment_id` | integer | Yes | Comment ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Contact Properties See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/contacts). ## list\_properties List properties (service addresses) across the workspace with optional search by street, city, state, or zip code. Returns linked contact IDs. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------- | | `search` | string | No | Search by street, city, state, or postal code | | `property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Filter by property type | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `sort_by` | `"id"`, `"property_type"`, `"created_at"`, `"modified_at"` | No | Field to sort by | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## list\_contact\_properties List all properties (service addresses) for a contact ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ---------- | | `contact_id` | integer | Yes | Contact ID | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_contact\_property Add a property (service address) to a contact ### Parameters | Field | Type / accepted values | Required | Details | | --------------------- | ------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------- | | `contact_id` | integer | Yes | Contact ID | | `address` | object | Yes | Property address | | `address.street` | string | No | Street address line 1 | | `address.street_2` | string | No | Street address line 2 | | `address.city` | string | No | City | | `address.state` | string | No | State/province | | `address.postal_code` | string | No | Postal/zip code | | `address.country` | string | No | Country | | `property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Property type (default: single-family) | | `role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | No | Contact role for this property (default: owner) | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_contact\_property Update a property for a contact ### Parameters | Field | Type / accepted values | Required | Details | | --------------------- | ------------------------------------------------------------------------------------------------- | -------- | ------------------------------ | | `contact_id` | integer | Yes | Contact ID | | `property_id` | JSON value | Yes | Property ID | | `address` | object | No | Property address | | `address.street` | string | No | Street address line 1 | | `address.street_2` | string | No | Street address line 2 | | `address.city` | string | No | City | | `address.state` | string | No | State/province | | `address.postal_code` | string | No | Postal/zip code | | `address.country` | string | No | Country | | `property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Property type | | `role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | No | Contact role for this property | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_contact\_property Delete a property from a contact ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ----------- | | `contact_id` | integer | Yes | Contact ID | | `property_id` | JSON value | Yes | Property ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Contact Tasks See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/contacts). ## list\_contact\_tasks List the follow-up tasks and reminders on a client, things like "call back Thursday" or "annual service due". Filter by status to see what is still outstanding. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | --------------------------------------------- | -------- | --------------------------------------------------------- | | `contact_id` | integer | Yes | Contact ID whose tasks to list. | | `status` | array of `"open"`, `"completed"`, `"overdue"` | No | Filter by one or more statuses: open, completed, overdue. | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_contact\_task Add a follow-up task or reminder to a client. Use this for anything the user wants to remember about a specific client that is not itself billable work, "follow up next week", "chase the deposit", "annual service due in March". For work that gets done and paid for, create a job instead. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact_id` | integer | Yes | Contact the task belongs to. | | `title` | string | Yes | Short title, e.g. "Call about deck quote". | | `description` | string | No | Any extra detail. | | `due_date` | string | No | When it is due, as an ISO timestamp. Reminders fire relative to this, so include a time, "Thursday" alone is not enough. Required for a recurring task. | | `assignee_id` | string | No | UUID of the team member responsible. Use list\_users to resolve a name. Format: `uuid`. | | `reminders_enabled` | boolean | No | Whether the assignee is notified as the due date approaches. Defaults to FALSE, so a task created without this is a silent to-do that nobody is nudged about. Turn it on when the user says "remind me". | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for a reminder that repeats (a yearly service check, a monthly courtesy call). Default: 'single'. | | `repeat_frequency` | integer | No | Interval, used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats: daily, weekly, monthly, yearly, or custom. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Unit the custom interval counts in. Only used with repeat\_frequency\_unit='custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long it repeats. With repeat\_duration\_unit='occurrences' this is a count. Required unless it repeats forever. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How it ends: 'forever', 'occurrences', or a time unit. Default: forever. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_contact\_task Edit a follow-up task. To mark one done, use complete\_contact\_task instead, for a repeating reminder, that completes just the one occurrence rather than touching the series. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact_id` | integer | Yes | Contact the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | | `title` | string | No | New title. | | `description` | string | No | New description. | | `due_date` | string | No | New due date as an ISO timestamp. | | `assignee_id` | string | No | UUID of the team member responsible. Use list\_users to resolve names. Format: `uuid`. | | `reminders_enabled` | boolean | No | Whether the assignee is notified as the due date approaches. Defaults to FALSE, so a task created without this is a silent to-do that nobody is nudged about. Turn it on when the user says "remind me". | | `update_scope` | `"single"`, `"all"`, `"recreate"` | No | For a repeating reminder: 'single' changes only this one, 'all' applies to future ones too, 'recreate' rebuilds the upcoming series after a pattern change. Ask which the user means. Default: single. | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for a reminder that repeats (a yearly service check, a monthly courtesy call). Default: 'single'. | | `repeat_frequency` | integer | No | Interval, used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats: daily, weekly, monthly, yearly, or custom. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Unit the custom interval counts in. Only used with repeat\_frequency\_unit='custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long it repeats. With repeat\_duration\_unit='occurrences' this is a count. Required unless it repeats forever. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How it ends: 'forever', 'occurrences', or a time unit. Default: forever. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## complete\_contact\_task Mark a follow-up task done, or reopen one. For a repeating reminder this affects only the single occurrence, leaving the rest of the series in place. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ------------------------------------ | -------- | ----------------------------------------------------------------- | | `contact_id` | integer | Yes | Contact the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | | `status` | `"open"`, `"completed"`, `"overdue"` | Yes | completed to mark it done, open to reopen it, overdue to flag it. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_contact\_task Delete a follow-up task. To record that it was done rather than remove it, use complete\_contact\_task, deleting loses the history. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ---------------------------- | | `contact_id` | integer | Yes | Contact the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Contacts See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/contacts). ## list\_contacts List contacts with optional pagination and search ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by name, email, phone, or company | | `sort_by` | `"id"`, `"first_name"`, `"last_name"`, `"email"`, `"phone"`, `"company"`, `"type"`, `"created_at"`, `"modified_at"` | No | Field to sort by. There is no "name" field, use first\_name or last\_name. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Filter by contact type | | `owner_id` | string | No | Filter by owner user UUID. Use list\_users to resolve names. Format: `uuid`. | | `tag_ids` | array of integer | No | Filter to records carrying ANY of these tag IDs (not all). Use list\_tags with the matching taggable\_type to resolve tag names to IDs first. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_contact Create a new contact ### Parameters | Field | Type / accepted values | Required | Details | | -------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- | | `first_name` | string | Yes | First name (required) | | `last_name` | string | Yes | Last name (required) | | `email` | string | No | Primary email address | | `phone` | string | No | Primary phone number | | `secondary_email` | string | No | Secondary email address | | `secondary_phone` | string | No | Secondary phone number | | `company` | string | No | Company name | | `company_department` | string | No | Department within company | | `company_position` | string | No | Position/title at company | | `title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` | No | Honorific title | | `type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Contact type (default: none) | | `owner_id` | string | No | UUID of the contact owner. Use list\_users to resolve names. Format: `uuid`. | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_contact Get a single contact by ID with expanded details (properties, activity) ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Contact ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_contact Update an existing contact ### Parameters | Field | Type / accepted values | Required | Details | | -------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- | | `id` | integer | Yes | Contact ID | | `first_name` | string | No | First name | | `last_name` | string | No | Last name | | `email` | string | No | Primary email address | | `phone` | string | No | Primary phone number | | `secondary_email` | string | No | Secondary email address | | `secondary_phone` | string | No | Secondary phone number | | `company` | string | No | Company name | | `company_department` | string | No | Department within company | | `company_position` | string | No | Position/title at company | | `title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` | No | Honorific title | | `type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Contact type | | `owner_id` | string | No | UUID of the contact owner. Use list\_users to resolve names. Format: `uuid`. | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_contact Delete a client. Their properties, follow-up tasks, notes and saved payment methods cascade away with them. Jobs, quotes and invoices remain but are left without a client. Confirm with the user first, especially when the client has live work, there is no undo. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Contact ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Equipment See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/equipment). ## list\_equipment List the company's own equipment, vehicles, trailers, tools and machines the business owns. This is NOT equipment installed at a customer's property. ### Parameters | Field | Type / accepted values | Required | Details | | ---------- | --------------------------------------------------------- | -------- | ---------------------------------------------------- | | `status` | array of `"active"`, `"in_repair"`, `"retired"`, `"sold"` | No | Filter by status: active, in\_repair, retired, sold. | | `search` | string | No | Search by name, make, model or serial number. | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_equipment Get one piece of company equipment with its details and assignee. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------------ | | `id` | integer | Yes | Equipment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## create\_equipment Add a piece of company equipment, a truck, trailer, tool or machine the business owns. Leave `name` off and it is derived from year/make/model, falling back to the serial number and then the category. Setting `replacement_date` automatically creates a "Replace \" reminder task. ### Parameters | Field | Type / accepted values | Required | Details | | --------------------- | ---------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No | Display name. Omit to derive it from year, make and model. | | `category` | `"vehicle"`, `"trailer"`, `"tool"`, `"machine"`, `"other"` | No | Default: other. | | `status` | `"active"`, `"in_repair"`, `"retired"`, `"sold"` | No | Default: active. | | `description` | string | No | | | `make` | string | No | Manufacturer, e.g. Ford. | | `model` | string | No | Model, e.g. F-250. | | `year` | integer | No | Model year. | | `serial_number` | string | No | Serial or VIN. | | `purchase_date` | string | No | YYYY-MM-DD. | | `purchase_price` | number | No | | | `warranty_expiration` | JSON value | No | YYYY-MM-DD. | | `warranty_number` | string | No | | | `replacement_date` | JSON value | No | When it is due for replacement (YYYY-MM-DD). Setting this creates a reminder task automatically; clearing it removes that task if still open. | | `assignee_id` | string | No | UUID of the team member responsible. Use list\_users to resolve a name. Format: `uuid`. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_equipment Update a piece of company equipment. Send only the fields that change, the server merges them over the existing record, so a status change on its own will not disturb the name or other details. ### Parameters | Field | Type / accepted values | Required | Details | | --------------------- | ---------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------- | | `id` | integer | Yes | Equipment ID | | `name` | string | No | | | `category` | `"vehicle"`, `"trailer"`, `"tool"`, `"machine"`, `"other"` | No | | | `status` | `"active"`, `"in_repair"`, `"retired"`, `"sold"` | No | active, in\_repair, retired or sold. | | `description` | string | No | | | `make` | string | No | | | `model` | string | No | | | `year` | integer | No | | | `serial_number` | string | No | | | `purchase_date` | string | No | YYYY-MM-DD. | | `purchase_price` | number | No | | | `warranty_expiration` | JSON value | No | YYYY-MM-DD. | | `warranty_number` | string | No | | | `replacement_date` | JSON value | No | Changing this moves the automatic replacement reminder task. | | `assignee_id` | string | No | UUID of the responsible team member. Use list\_users to resolve names. Format: `uuid`. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_equipment Delete a piece of company equipment, along with its tasks and service log. To take something out of service without losing its history, set status to "retired" or "sold" instead. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------------ | | `id` | integer | Yes | Equipment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_equipment\_tasks List maintenance tasks on a piece of company equipment, services, inspections, and the automatic replacement reminder. ### Parameters | Field | Type / accepted values | Required | Details | | -------------- | --------------------------------------------- | -------- | ------------------------------------------- | | `equipment_id` | integer | Yes | Equipment ID | | `status` | array of `"open"`, `"completed"`, `"overdue"` | No | Filter by status: open, completed, overdue. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_equipment\_task Add a maintenance task to a piece of company equipment, an oil change, an inspection, a repair to book. Use recurrence for scheduled servicing that repeats. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `equipment_id` | integer | Yes | Equipment the task belongs to. | | `title` | string | Yes | e.g. "Oil change" or "Annual DOT inspection". | | `description` | string | No | | | `due_date` | string or string | No | When it is due, as an ISO timestamp. Required for a recurring task, the series repeats from this date. | | `assignee_id` | string | No | UUID of the team member responsible. Use list\_users to resolve names. Format: `uuid`. | | `reminders_enabled` | boolean | No | Whether the assignee is notified as the due date approaches. Defaults to FALSE, so without it the task is a silent to-do. | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for scheduled maintenance that repeats, an oil change every 3 months, an annual inspection. Default: 'single'. | | `repeat_frequency` | integer | No | Interval, used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Unit the custom interval counts in. Only used with repeat\_frequency\_unit='custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long it repeats. Required unless repeat\_duration\_unit is 'forever'. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How it ends: 'forever', 'occurrences', or a time unit. Default: forever. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_equipment\_task Edit a maintenance task. To mark one done use complete\_equipment\_task, which for repeating servicing completes only that occurrence. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `equipment_id` | integer | Yes | Equipment the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | | `title` | string | No | | | `description` | string | No | | | `due_date` | string or string | No | ISO timestamp with timezone. | | `assignee_id` | string | No | UUID of the team member responsible. Use list\_users to resolve names. Format: `uuid`. | | `reminders_enabled` | boolean | No | | | `update_scope` | `"single"`, `"all"`, `"recreate"` | No | For repeating servicing: 'single' changes this one, 'all' applies to future ones, 'recreate' rebuilds the schedule after a pattern change. Default: single. | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for scheduled maintenance that repeats, an oil change every 3 months, an annual inspection. Default: 'single'. | | `repeat_frequency` | integer | No | Interval, used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Unit the custom interval counts in. Only used with repeat\_frequency\_unit='custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long it repeats. Required unless repeat\_duration\_unit is 'forever'. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How it ends: 'forever', 'occurrences', or a time unit. Default: forever. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## complete\_equipment\_task Mark a maintenance task done, or reopen it. For repeating servicing this affects only the single occurrence. ### Parameters | Field | Type / accepted values | Required | Details | | -------------- | ------------------------------------ | -------- | ------------------------------ | | `equipment_id` | integer | Yes | Equipment the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | | `status` | `"open"`, `"completed"`, `"overdue"` | Yes | completed, open or overdue. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_equipment\_task Delete a maintenance task. To record that it was done, use complete\_equipment\_task instead, deleting loses the history. ### Parameters | Field | Type / accepted values | Required | Details | | -------------- | ---------------------- | -------- | ------------------------------ | | `equipment_id` | integer | Yes | Equipment the task belongs to. | | `task_id` | JSON value | Yes | Task ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_equipment\_notes Read the service log for a piece of company equipment: notes, maintenance, repairs and inspections. ### Parameters | Field | Type / accepted values | Required | Details | | -------------- | ----------------------------------------------------- | -------- | ------------------------------------------------------------------- | | `equipment_id` | integer | Yes | Equipment ID | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Filter to one kind of entry: note, maintenance, repair, inspection. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_equipment\_note Record work done on a piece of company equipment, a repair, a service, an inspection result, or a plain note. This is the equipment history, so record what happened here rather than only completing the task. ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `equipment_id` | integer | Yes | Equipment the entry belongs to. | | `title` | string | Yes | e.g. "Replaced brake pads". | | `content` | string | No | The detail of what was done. | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | note, maintenance, repair or inspection. Default: note. | | `activity_date` | string or string | No | When the work actually happened, if not today. Logging a repair a week later should carry the repair date, not the date it was typed up. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_equipment\_note Edit an entry in a piece of equipment's service log. ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | ----------------------------------------------------- | -------- | ---------------------------------------------------------- | | `equipment_id` | integer | Yes | Equipment the entry belongs to. | | `note_id` | JSON value | Yes | Note ID. | | `title` | string | No | | | `content` | string | No | | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | | | `activity_date` | string or string | No | When the work happened, as an ISO timestamp with timezone. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_equipment\_note Delete an entry from a piece of equipment's service log. ### Parameters | Field | Type / accepted values | Required | Details | | -------------- | ---------------------- | -------- | ------------------------------- | | `equipment_id` | integer | Yes | Equipment the entry belongs to. | | `note_id` | JSON value | Yes | Note ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Events See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/events). ## list\_events List events (appointments, tasks, visits) with optional pagination, search, and filters ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ----------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search events | | `sort_by` | `"id"`, `"name"`, `"status"`, `"type"`, `"start_date"`, `"end_date"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `start_date` | string | No | Filter: start date (YYYY-MM-DD) | | `end_date` | JSON value | No | Filter: end date (YYYY-MM-DD) | | `job_id` | integer | No | Filter by job ID | | `type` | `"appointment"`, `"task"`, `"event"` | No | Filter by event type | | `assignee_ids` | array of string | No | Filter by assignee UUIDs. Use list\_users to resolve names to UUIDs. | | `status` | array of `"pending"`, `"in_progress"`, `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | No | Filter by one or more statuses. | | `request_id` | JSON value | No | Filter by request ID | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_event Create a new event. Three kinds: an APPOINTMENT is a visit for an existing job or request, a TASK is a to-do on the calendar, an EVENT is a standalone calendar entry. Recurrence (recurrence\_type="recurring") applies to tasks and events only, appointments cannot repeat on their own. For repeating visits, put the job on a recurring schedule with setup\_recurring\_job, which also sets the job type and billing settings. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | No | Event name/title | | `description` | string | No | Event description | | `job_id` | integer | No | Associated job ID | | `request_id` | JSON value | No | Associated request ID | | `start_date` | string | Yes | Start date (YYYY-MM-DD) | | `end_date` | JSON value | Yes | End date (YYYY-MM-DD) | | `start_time` | string | No | Start time (HH:MM) in workspace timezone. Required for timed events; omit only for all\_day=true or an update retaining the saved time. | | `end_time` | JSON value | No | End time (HH:MM) in workspace timezone, after the start. Required for timed events; omit only for all\_day=true or an update retaining the saved time. | | `all_day` | boolean | No | Whether this is an all-day event (default: false) | | `multi_day` | JSON value | No | Whether this spans multiple days (default: false) | | `type` | `"appointment"`, `"task"`, `"event"` | No | Event type (default: event) | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | No | Event status (default: scheduled) | | `is_flexible` | JSON value | No | Whether the visit can be moved within the day by scheduling. | | `duration_minutes` | integer | No | Expected length of the visit in minutes. | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for a repeating task or standalone event. Default: 'single'. NOT valid for type='appointment', repeating visits come from setup\_recurring\_job. The repeat\_\* fields below only apply when this is 'recurring'. | | `repeat_frequency` | JSON value | No | Interval between occurrences, used when repeat\_frequency\_unit is 'custom' (e.g. 2 with a custom weekly unit means every 2 weeks). | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats: daily, weekly, monthly, yearly, or 'custom' to combine repeat\_frequency with repeat\_frequency\_unit\_custom. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | The unit the custom interval counts in. Only used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those patterns. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | Days to repeat on: 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long the series runs. With repeat\_duration\_unit='occurrences' this is a visit count. Required unless the series repeats forever. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How the series ends: 'forever' (no end), 'occurrences' (a fixed number of visits, set in repeat\_duration), or a time unit. Default: forever. | | `assignee_ids` | array of string | No | Array of assignee UUIDs. Use list\_users to resolve names. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_event Get a single event by ID with expanded details ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | -------- | | `id` | integer | Yes | Event ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_event Update an existing event. For an occurrence of a recurring appointment, set update\_scope to say whether the change applies to just this visit, all future visits, or should rebuild the upcoming schedule. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Event ID | | `name` | string | No | Event name/title | | `description` | string | No | Event description | | `job_id` | JSON value | No | Associated job ID | | `request_id` | JSON value | No | Associated request ID | | `start_date` | string | No | Start date (YYYY-MM-DD) | | `end_date` | JSON value | No | End date (YYYY-MM-DD) | | `start_time` | string | No | Start time (HH:MM) in workspace timezone. Required for timed events; omit only for all\_day=true or an update retaining the saved time. | | `end_time` | JSON value | No | End time (HH:MM) in workspace timezone, after the start. Required for timed events; omit only for all\_day=true or an update retaining the saved time. | | `all_day` | boolean | No | Whether this is an all-day event | | `multi_day` | JSON value | No | Whether this spans multiple days | | `type` | `"appointment"`, `"task"`, `"event"` | No | Event type | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | No | Event status | | `is_flexible` | JSON value | No | Whether the visit can be moved within the day by scheduling. | | `duration_minutes` | integer | No | Expected length of the visit in minutes. | | `recurrence_type` | `"single"`, `"recurring"` | No | Set to 'recurring' for a repeating task or standalone event. Default: 'single'. NOT valid for type='appointment', repeating visits come from setup\_recurring\_job. The repeat\_\* fields below only apply when this is 'recurring'. | | `repeat_frequency` | JSON value | No | Interval between occurrences, used when repeat\_frequency\_unit is 'custom' (e.g. 2 with a custom weekly unit means every 2 weeks). | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often it repeats: daily, weekly, monthly, yearly, or 'custom' to combine repeat\_frequency with repeat\_frequency\_unit\_custom. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | The unit the custom interval counts in. Only used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those patterns. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | Days to repeat on: 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long the series runs. With repeat\_duration\_unit='occurrences' this is a visit count. Required unless the series repeats forever. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How the series ends: 'forever' (no end), 'occurrences' (a fixed number of visits, set in repeat\_duration), or a time unit. Default: forever. | | `update_scope` | `"single"`, `"all"`, `"recreate"` | No | Which occurrences of a recurring appointment this update touches. 'single' changes only this visit, 'all' applies to this and future visits, 'recreate' rebuilds the upcoming schedule after a pattern change. Default: single. Ask the user which they mean rather than guessing. | | `assignee_ids` | array of string | No | Array of assignee UUIDs. Use list\_users to resolve names. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## set\_visit\_line\_items Set the line items for ONE visit of a job, so that visit bills differently from the rest, the parts used on this trip, an extra callout. When the appointment is completed, its invoice is built from these instead of the job's line items; discount, tax and deposit still come from the job. Send line\_items as null to clear the override and bill from the job again. This affects only the single visit, never the whole recurring schedule. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | --------------------------------- | -------- | --------------------------------------------------------------------------- | | `event_id` | integer | Yes | The appointment (visit) to price. | | `line_items` | array of object or object or null | Yes | Line items for this visit only, or null to go back to billing from the job. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_event Delete an event by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | -------- | | `id` | integer | Yes | Event ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_event\_status Update only the status of an event. Use this instead of update\_event when changing status, as status transitions may trigger business logic. ### Parameters | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------------------------- | -------- | ---------- | | `id` | integer | Yes | Event ID | | `status` | `"scheduled"`, `"cancelled"`, `"missed"`, `"completed"` | Yes | New status | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Expenses See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/jobs). ## list\_job\_expenses List expenses for a job. Expenses track costs associated with jobs (materials, subcontractors, permits, etc.). ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------------------------------------------------------- | -------- | --------------------------------------------------------------- | | `job_id` | integer | Yes | Job ID | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by name or description | | `sort_by` | `"id"`, `"name"`, `"total"`, `"date"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_job\_expense Create a new expense for a job. Expenses track costs such as materials, subcontractor fees, permits, etc. Provide a receipt via `receipt` (a user-attached file from the chat) or `attachment` (a storage path from a prior upload\_file call). ### Parameters | Field | Type / accepted values | Required | Details | | ---------------------- | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job_id` | integer | Yes | Job ID | | `name` | string | Yes | Expense name/title | | `description` | string | No | Expense description. Omit when not needed. | | `date` | string | No | Expense date (YYYY-MM-DD) | | `total` | number | Yes | Expense amount | | `receipt` | object | No | A receipt file attached by the user in the chat. Preferred over `attachment` when the host populates it. The file is uploaded automatically and linked to the expense. | | `receipt.download_url` | string | When parent supplied | HTTPS URL the server can fetch the file bytes from. Format: `uri`. | | `receipt.file_id` | string | When parent supplied | Opaque file identifier from the host. | | `receipt.file_name` | string | No | Original filename, if the host provides it. | | `receipt.mime_type` | string | No | MIME type hint from the host. Validated against the server allowlist after fetch. | | `attachment` | string | No | Storage path of an already-uploaded file (from a prior upload\_file call). Use only when not providing `receipt`. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_job\_expense Get a single expense by ID for a job ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ---------- | | `job_id` | integer | Yes | Job ID | | `expense_id` | JSON value | Yes | Expense ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_job\_expense Update an existing expense for a job. Provide a new receipt via `receipt` (a user-attached file from the chat) or `attachment` (a storage path from a prior upload\_file call). Pass `attachment: ""` to clear the existing receipt. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------------- | ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `job_id` | integer | Yes | Job ID | | `expense_id` | JSON value | Yes | Expense ID | | `name` | string | No | Expense name/title | | `description` | string | No | Expense description | | `date` | string | No | Expense date (YYYY-MM-DD) | | `total` | number | No | Expense amount | | `receipt` | object | No | A new receipt file attached by the user in the chat. Replaces any existing receipt. Preferred over `attachment` when the host populates it. | | `receipt.download_url` | string | When parent supplied | HTTPS URL the server can fetch the file bytes from. Format: `uri`. | | `receipt.file_id` | string | When parent supplied | Opaque file identifier from the host. | | `receipt.file_name` | string | No | Original filename, if the host provides it. | | `receipt.mime_type` | string | No | MIME type hint from the host. Validated against the server allowlist after fetch. | | `attachment` | string | No | Storage path of an already-uploaded file, or empty string to clear the existing receipt. Use only when not providing `receipt`. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_job\_expense Delete an expense from a job ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | ---------------------- | -------- | ---------- | | `job_id` | integer | Yes | Job ID | | `expense_id` | JSON value | Yes | Expense ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Help See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. ## list\_help\_guides Index of BlueSuite product help articles. Use it to find guides relevant to a user's how-to question before fetching the best match. ### Parameters No parameters. ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | -------------------------------------------------------- | | `data` | string | Yes | The tool's answer as text, meant to be read and relayed. | ## get\_help\_article Fetch a BlueSuite product help article by the guide ID returned from list\_help\_guides. Use its content to answer the user and cite its source link. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------------- | | `id` | integer | Yes | Help guide ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | -------------------------------------------------------- | | `data` | string | Yes | The tool's answer as text, meant to be read and relayed. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Tool reference 146 tools are registered. Your host may cache an earlier tool list; reconnect or refresh discovery to see newly added tools. | Tools | Operations | | ---------------------------------------------------------- | ---------- | | [Comments](/docs/mcp/tools/comments) | 4 | | [Contact Properties](/docs/mcp/tools/contact-properties) | 5 | | [Contact Tasks](/docs/mcp/tools/contact-tasks) | 5 | | [Contacts](/docs/mcp/tools/contacts) | 5 | | [Equipment](/docs/mcp/tools/equipment) | 14 | | [Events](/docs/mcp/tools/events) | 7 | | [Expenses](/docs/mcp/tools/expenses) | 5 | | [Help](/docs/mcp/tools/help) | 2 | | [Installed Equipment](/docs/mcp/tools/installed-equipment) | 14 | | [Invoices](/docs/mcp/tools/invoices) | 6 | | [Jobs](/docs/mcp/tools/jobs) | 10 | | [Leads](/docs/mcp/tools/leads) | 1 | | [Notifications](/docs/mcp/tools/notifications) | 1 | | [Onboarding](/docs/mcp/tools/onboarding) | 1 | | [Payments](/docs/mcp/tools/payments) | 5 | | [Quote Templates](/docs/mcp/tools/quote-templates) | 6 | | [Quotes](/docs/mcp/tools/quotes) | 6 | | [Requests](/docs/mcp/tools/requests) | 7 | | [Reviews](/docs/mcp/tools/reviews) | 2 | | [Service Plans](/docs/mcp/tools/service-plans) | 10 | | [Services](/docs/mcp/tools/services) | 5 | | [Tags](/docs/mcp/tools/tags) | 6 | | [Tax Rates](/docs/mcp/tools/tax-rates) | 5 | | [Time Entries](/docs/mcp/tools/time-entries) | 4 | | [Timesheets](/docs/mcp/tools/timesheets) | 3 | | [Uploads](/docs/mcp/tools/uploads) | 5 | | [Users](/docs/mcp/tools/users) | 1 | | [Workspace](/docs/mcp/tools/workspace) | 1 | [Download the complete input and output schemas](/mcp-tools.json). # Installed Equipment See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/installed-equipment). ## list\_installed\_equipment List equipment installed at customers' properties, furnaces, water heaters, irrigation controllers and so on. This is NOT the company's own trucks and tools; use list\_equipment for those. Filter by property or contact to see what is installed for one client. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ----------------------------------------------------------------- | -------- | ----------------------------------------------------------------- | | `property_id` | integer | No | Only units at this property. | | `contact_id` | JSON value | No | Only units belonging to this client, across all their properties. | | `status` | array of `"active"`, `"needs_service"`, `"replaced"`, `"removed"` | No | Filter by status: active, needs\_service, replaced, removed. | | `search` | string | No | Search by name, make, model or serial number. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_installed\_equipment Get one installed unit with its property, client and details. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------------------- | | `id` | integer | Yes | Installed equipment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## create\_installed\_equipment Record a unit installed at a customer's property. `property_id` is required, a unit is always installed somewhere. Leave `name` off and it is derived from make and model, then the equipment type. Setting warranty or replacement dates schedules reminders automatically. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | -------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `property_id` | integer | Yes | Property the unit is installed at. Required. Use list\_properties or list\_contact\_properties to find it. | | `contact_id` | integer | No | Client who owns it, if different from the property owner. | | `name` | string | No | Omit to derive from make and model. | | `equipment_type` | string | No | e.g. "Furnace", "Water heater". | | `make` | string | No | | | `model` | string | No | | | `serial_number` | string | No | | | `description` | string | No | | | `status` | `"active"`, `"needs_service"`, `"replaced"`, `"removed"` | No | Default: active. | | `install_date` | string | No | YYYY-MM-DD. | | `warranty_expiration` | JSON value | No | YYYY-MM-DD. | | `warranty_number` | string | No | | | `replacement_date` | JSON value | No | YYYY-MM-DD. | | `auto_reminders_enabled` | boolean | No | Whether warranty and replacement reminders are scheduled automatically from the dates above. Defaults to TRUE, so a unit with a warranty date will generate reminders unless this is turned off. | | `installed_by_job_id` | JSON value | No | The job that installed it. Also links the unit to that job history. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_installed\_equipment Update an installed unit. Send only what changes, the server merges it over the existing record, so a status change alone will not disturb the name or which property it is at. Changing warranty or replacement dates re-syncs the automatic reminders. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | -------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Installed equipment ID | | `property_id` | integer | No | Move it to a different property. | | `contact_id` | JSON value | No | | | `name` | string | No | | | `equipment_type` | string | No | | | `make` | string | No | | | `model` | string | No | | | `serial_number` | string | No | | | `description` | string | No | | | `status` | `"active"`, `"needs_service"`, `"replaced"`, `"removed"` | No | active, needs\_service, replaced or removed. Use replaced or removed rather than deleting, so the history survives. | | `install_date` | string | No | YYYY-MM-DD. | | `warranty_expiration` | JSON value | No | YYYY-MM-DD. | | `warranty_number` | string | No | | | `replacement_date` | JSON value | No | YYYY-MM-DD. | | `auto_reminders_enabled` | boolean | No | | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_installed\_equipment Delete an installed unit and its service history. To record that it was taken out or swapped, set status to 'removed' or 'replaced' instead, that keeps the history for the property. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------------------- | | `id` | integer | Yes | Installed equipment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_installed\_equipment\_notes Read the service history for an installed unit: notes, maintenance, repairs and inspections. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ----------------------------------------------------- | -------- | ---------------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Filter to one kind of entry. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_installed\_equipment\_note Record work done on an installed unit, a service, a repair, an inspection result. This is the unit's history at that property and outlives any one job. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ----------------------------------------------------- | -------- | ---------------------------------------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `title` | string | Yes | e.g. "Replaced igniter". | | `content` | string | No | | | `type` | `"note"`, `"maintenance"`, `"repair"`, `"inspection"` | No | Default: note. | | `activity_date` | string or string | No | When the work happened, if not when it was recorded. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_installed\_equipment\_note Delete an entry from an installed unit's service history. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ---------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `note_id` | JSON value | Yes | Note ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_installed\_equipment\_reminders List the scheduled reminders on an installed unit. Warranty and replacement reminders are maintained automatically from the unit's dates; custom ones are added by hand. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ------------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_installed\_equipment\_reminder Schedule a one-off reminder about an installed unit. Do NOT create warranty or replacement reminders this way, those are generated and kept in step automatically from the unit's dates, so a hand-made one duplicates them. Use this for anything else, and note it can go to the customer, the team, or both. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ----------------------------------------- | -------- | -------------------------------------------------------------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `name` | string | Yes | Internal label for the reminder. | | `subject` | string | Yes | Subject line of the message. | | `message` | string | Yes | Body of the message. | | `remind_on` | string | Yes | Date it fires (YYYY-MM-DD). | | `reason` | `"warranty"`, `"replacement"`, `"custom"` | No | Leave as 'custom'. 'warranty' and 'replacement' are managed automatically. | | `send_customer` | boolean | No | Send it to the client. Default: false. | | `send_internal` | JSON value | No | Send it to the team. Default: true. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_installed\_equipment\_reminder Delete a scheduled reminder on an installed unit. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ---------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `notification_id` | JSON value | Yes | Reminder ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_installed\_equipment\_jobs List the jobs that have touched an installed unit, its service history by job. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ---------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## link\_installed\_equipment\_to\_job Link an installed unit to a job, recording that the job worked on it. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ---------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `job_id` | JSON value | Yes | Job ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## unlink\_installed\_equipment\_from\_job Remove the link between a job and an installed unit. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------- | ---------------------- | | `installed_equipment_id` | integer | Yes | Installed equipment ID | | `job_id` | JSON value | Yes | Job ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Invoices See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/invoices). ## list\_invoices List invoices with optional pagination, search, and filters ### Parameters | Field | Type / accepted values | Required | Details | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search invoices | | `sort_by` | `"id"`, `"number"`, `"name"`, `"status"`, `"type"`, `"total"`, `"date"`, `"due_date"`, `"send_on"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `contact_id` | integer | No | Filter by contact ID | | `job_id` | JSON value | No | Filter by job ID | | `exclude_children` | boolean | No | Exclude child invoices (recurring children) | | `exclude_statuses` | array of `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | No | Exclude invoices with any of these statuses. | | `tag_ids` | array of JSON value | No | Filter to records carrying ANY of these tag IDs (not all). Use list\_tags with the matching taggable\_type to resolve tag names to IDs first. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_invoice Create a new invoice ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | -------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No | Invoice name/title | | `contact_id` | integer | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `job_id` | JSON value | No | Associated job ID | | `quote_id` | JSON value | No | Associated quote ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `date` | string | No | Invoice date (YYYY-MM-DD) | | `due_date` | JSON value | No | Due date (YYYY-MM-DD) | | `send_on` | JSON value | No | Scheduled send date (YYYY-MM-DD) | | `message` | string | No | Message to client | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Payment terms (default: due\_on\_receipt) | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount (default: 0) | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). Default: 'percent'. | | `deposit` | number | No | Deposit amount (default: 0) | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. Default: 'percent'. | | `tax_rate` | number | No | Tax rate percentage (default: 0) | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Invoice total | | `reminders_enabled` | boolean | No | Whether BlueSuite automatically emails the client reminders about this invoice. Default: true. | | `waive_credit_card_fee` | JSON value | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Default: false. | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | No | Invoice status (default: draft). Record payments with create\_payment; paid is rejected while a balance remains. | | `type` | `"one-off"`, `"recurring"` | No | Invoice type. Only 'one-off' can be created, recurring invoices are no longer supported. For work that repeats, use setup\_recurring\_job instead; each completed visit bills as its own invoice. | | `event_ids` | array of JSON value | No | Event IDs to associate with this invoice | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_invoice Get a single invoice by ID with expanded details, including the payments recorded against it and `amount_due`, what the client still owes. Use `amount_due` for balance questions rather than subtracting from `total` yourself; it is derived from the line items, and the stored `total` can be stale. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Invoice ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_invoice Update an existing invoice ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | -------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Invoice ID | | `name` | string | No | Invoice name/title | | `contact_id` | JSON value | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `job_id` | JSON value | No | Associated job ID | | `quote_id` | JSON value | No | Associated quote ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `date` | string | No | Invoice date (YYYY-MM-DD) | | `due_date` | JSON value | No | Due date (YYYY-MM-DD) | | `send_on` | JSON value | No | Scheduled send date (YYYY-MM-DD) | | `message` | string | No | Message to client | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Payment terms | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). | | `deposit` | number | No | Deposit amount | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. | | `tax_rate` | number | No | Tax rate percentage | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Invoice total | | `reminders_enabled` | boolean | No | Whether BlueSuite automatically emails the client reminders about this invoice. Default: true. | | `waive_credit_card_fee` | JSON value | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Default: false. | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | No | Invoice status. To mark paid, record a payment using create\_payment with settle\_remaining=true. | | `type` | `"one-off"`, `"recurring"` | No | Invoice type | | `event_ids` | array of JSON value | No | Event IDs to associate | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_invoice Delete an invoice by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Invoice ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_invoice\_status Record a change to an invoice's status. This does NOT email anyone, marking an invoice "sent" sets its due date and notifies the business internally, but the client receives nothing. To actually deliver an invoice to the client, use send\_notification with type="invoice\_to\_customer", which emails and texts the client according to the workspace notification settings and moves the invoice to sent. Use this tool for cancelling an invoice or correcting a status. To mark an invoice paid, use create\_payment with object\_type="invoice", category="payment", and settle\_remaining=true. A paid status update is rejected if the invoice still has an outstanding balance. ### Parameters | Field | Type / accepted values | Required | Details | | -------- | -------------------------------------------------------------------------------------------- | -------- | ---------- | | `id` | integer | Yes | Invoice ID | | `status` | `"draft"`, `"sent"`, `"paid"`, `"overdue"`, `"cancelled"`, `"partially_paid"`, `"scheduled"` | Yes | New status | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Jobs See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/jobs). ## list\_jobs List jobs with optional pagination and search ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | --------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search jobs | | `sort_by` | `"id"`, `"number"`, `"name"`, `"status"`, `"type"`, `"total"`, `"expected_end_date"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `contact_id` | integer | No | Filter by contact ID | | `quote_id` | JSON value | No | Filter by quote ID | | `status` | array of `"draft"`, `"scheduled"`, `"in_progress"`, `"on_hold"`, `"completed"`, `"cancelled"` | No | Filter by one or more statuses. | | `derived_state` | array of `"missed"`, `"overdue"`, `"action_required"`, `"needs_invoicing"`, `"unscheduled"`, `"awaiting_payment"` | No | Filter by one or more derived conditions: needs\_invoicing (work done, not yet invoiced), unscheduled (no appointment booked), missed, overdue, awaiting\_payment, action\_required. Use this for questions about what needs attention rather than filtering on status. | | `tag_ids` | array of JSON value | No | Filter to records carrying ANY of these tag IDs (not all). Use list\_tags with the matching taggable\_type to resolve tag names to IDs first. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_job Create a new job ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ----------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No | Job name/title | | `contact_id` | integer | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `request_id` | JSON value | No | Associated request ID | | `quote_id` | JSON value | No | Associated quote ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount (default: 0) | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). Default: 'percent'. | | `deposit` | number | No | Deposit amount (default: 0) | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. Default: 'percent'. | | `tax_rate` | number | No | Tax rate percentage (default: 0) | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Job total | | `type` | `"one-off"`, `"recurring"` | No | Job type (default: one-off) | | `status` | `"completed"`, `"cancelled"` | No | Initial status. Only completed and cancelled can be set; otherwise status follows the job's appointments. | | `billing_type` | `"per_visit"`, `"manual"` | No | How the job gets invoiced. 'per\_visit' automatically creates and sends an invoice each time an appointment is completed. 'manual' means the business invoices on its own schedule. Defaults to 'per\_visit', ask the user which they want for recurring work rather than assuming, because the default sends real invoices to their client. | | `invoice_autopay` | boolean | No | Charge the client's saved payment method automatically when an invoice is auto-created. Only meaningful with billing\_type='per\_visit'. Default: false. | | `invoice_payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Payment terms applied to invoices generated from this job. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Nothing is charged against a job directly; the waiver rides onto invoices created from it. Default: false. | | `expected_end_date` | string | No | Expected end date (YYYY-MM-DD) | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_job Get a single job by ID with expanded details ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------- | | `id` | integer | Yes | Job ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_job Update an existing job. For an in-progress job that requires change orders, line items, discounts, and taxes cannot be changed here; create a change order in the dashboard. Omit line\_items when only updating job details. Setting `status` here applies the same completion rules as `update_job_status` (for example, required forms must be filled in before a job can be completed), so prefer that tool when status is the only change. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ----------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Job ID | | `name` | string | No | Job name/title | | `status` | `"completed"`, `"cancelled"`, `"reopen"` | No | Change the job status as part of this update. Prefer `update_job_status` when status is the only thing changing. Draft, Scheduled and Ongoing are derived from appointments and cannot be set. | | `contact_id` | JSON value | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `request_id` | JSON value | No | Associated request ID | | `quote_id` | JSON value | No | Associated quote ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). | | `deposit` | number | No | Deposit amount | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. | | `tax_rate` | number | No | Tax rate percentage | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Job total | | `type` | `"one-off"`, `"recurring"` | No | Job type | | `billing_type` | `"per_visit"`, `"manual"` | No | How the job gets invoiced. 'per\_visit' automatically creates and sends an invoice each time an appointment is completed. 'manual' means the business invoices on its own schedule. Defaults to 'per\_visit', ask the user which they want for recurring work rather than assuming, because the default sends real invoices to their client. | | `invoice_autopay` | boolean | No | Charge the client's saved payment method automatically when an invoice is auto-created. Only meaningful with billing\_type='per\_visit'. | | `invoice_payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Payment terms applied to invoices generated from this job. | | `waive_credit_card_fee` | boolean | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Nothing is charged against a job directly; the waiver rides onto invoices created from it. Default: false. | | `expected_end_date` | string | No | Expected end date (YYYY-MM-DD) | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## setup\_recurring\_job Put a job on a recurring appointment schedule, weekly lawn care, quarterly maintenance, and so on. Use this rather than create\_event for anything tied to a job: it marks the job recurring, saves the billing settings the completion flow reads, and creates the appointment series in one step. A recurring job created without this stays unscheduled and never generates visits. Confirm the billing\_type with the user first, since per\_visit sends real invoices to their client after every visit. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Job ID to schedule. | | `start_date` | string | Yes | Date of the first appointment (YYYY-MM-DD). | | `end_date` | JSON value | No | End date of each appointment, for visits spanning more than one day. | | `start_time` | string | No | Start time (HH:MM). Required unless all\_day is true. | | `end_time` | JSON value | No | End time (HH:MM). Required unless all\_day is true. | | `all_day` | boolean | No | Whether each visit takes the whole day. | | `multi_day` | JSON value | No | Whether each visit spans multiple days. | | `is_flexible` | JSON value | No | Whether scheduling may move the visit within the day. | | `duration_minutes` | integer | No | Expected length of each visit in minutes. | | `assignee_ids` | array of string | No | UUIDs of the team members assigned. Use list\_users to resolve names. | | `billing_type` | `"per_visit"`, `"manual"` | No | How the job gets invoiced. 'per\_visit' automatically creates and sends an invoice each time an appointment is completed. 'manual' means the business invoices on its own schedule. Defaults to 'per\_visit', ask the user which they want for recurring work rather than assuming, because the default sends real invoices to their client. | | `payment_terms` | `"due_on_receipt"`, `"net_7"`, `"net_14"`, `"net_30"`, `"net_60"`, `"net_90"` | No | Payment terms on invoices generated from these visits. | | `repeat_frequency` | JSON value | No | Interval between visits, used when repeat\_frequency\_unit is 'custom' (2 with a custom weekly unit means every 2 weeks). Default: 1. | | `repeat_frequency_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How often the visit repeats: daily, weekly, monthly, yearly, or 'custom'. Default: weekly. | | `repeat_frequency_unit_custom` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | Unit the custom interval counts in. Only used when repeat\_frequency\_unit is 'custom'. | | `repeat_frequency_schedule` | object | No | Which days a custom weekly or monthly pattern lands on. Required for those patterns. | | `repeat_frequency_schedule.days` | array of JSON value | When parent supplied | Days to repeat on: 1-7 for weekdays, 1-31 for days of the month. | | `repeat_frequency_schedule.type` | `"weekDays"`, `"monthWeekDays"`, `"monthDays"` | When parent supplied | | | `repeat_duration` | JSON value | No | How long the schedule runs. With repeat\_duration\_unit='occurrences' this is a visit count. Required unless it repeats forever. | | `repeat_duration_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"`, `"forever"`, `"occurrences"` | No | How the schedule ends: 'forever', 'occurrences' (a fixed number of visits), or a time unit. Default: forever. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## end\_recurring\_job\_schedule Stop a job's recurring schedule from a given date. Use this when the user says to stop, cancel or end repeating work, do NOT delete the job, which would take its history with it. This is a cutoff, not a delete: visits after the date are removed, while visits on or before it, and anything already completed or invoiced, are kept. Confirm the date with the user, since "stop after this month" and "stop immediately" remove different visits. ### Parameters | Field | Type / accepted values | Required | Details | | ---------- | ---------------------- | -------- | ----------------------------------------------------------------------------- | | `id` | integer | Yes | Job ID whose schedule should end. | | `end_date` | string | Yes | Last date the schedule runs (YYYY-MM-DD). Visits after this date are removed. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_job Delete a job and everything that hangs off it. Every appointment cascades away, including completed visits, along with expenses and change orders; invoices and time entries survive but lose their link to the job. For a recurring job this destroys the whole visit history, to stop repeating work use end\_recurring\_job\_schedule, which keeps the past and only removes visits after a cutoff. Confirm with the user first; there is no undo. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------- | | `id` | integer | Yes | Job ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## hold\_job Place a job on hold with a reason, optional notes, and an optional follow-up date. Confirm whether upcoming unstarted appointments should be cancelled to release their time slots or kept booked. Completed and in-progress visits are preserved. Existing invoices and payments are unaffected. The person placing the hold receives email and dashboard follow-ups when due. For recurring jobs, cancel\_appointments=true also pauses new visits; false keeps existing appointments and continues normal generation. Service plans are not supported. ### Parameters | Field | Type / accepted values | Required | Details | | --------------------- | ----------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ | | `id` | integer | Yes | Job ID | | `reason` | `"customer"`, `"materials"`, `"approval"`, `"weather"`, `"other"` | Yes | | | `notes` | string | No | maxLength: 2000. | | `follow_up_on` | string | No | Optional follow-up date, YYYY-MM-DD. Does not automatically resume the job. | | `cancel_appointments` | boolean | Yes | Required explicit choice: true cancels upcoming unstarted appointments; false keeps them booked. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## resume\_job Resume an on-hold job. For recurring jobs whose hold\_cancel\_appointments is true and whose schedule has not been explicitly ended, resume\_on is required: visits restart on the original recurrence pattern from that date without catch-up visits. If all schedules have been explicitly ended, omit resume\_on; resuming clears the hold without restarting them. If hold\_cancel\_appointments is false, omit resume\_on; appointments continue unchanged. End dates and occurrence limits stay unchanged. Read the current job first and pass its held\_at timestamp so a stale request cannot resume a newer hold. Status is recalculated from existing work. Cancelled records stay in history. Recurring visits are generated again from resume\_on; one-off appointments must be scheduled separately. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------ | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Job ID | | `expected_held_at` | string | Yes | Exact held\_at timestamp from get\_job. Format: `date-time`. | | `resume_on` | string | No | Required only when hold\_cancel\_appointments is true on a recurring job with a schedule that has not been explicitly ended: resume appointments on or after this date (YYYY-MM-DD), today or later. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_job\_status Change a job's status with completion rules enforced. Use hold\_job and resume\_job to manage holds. Draft, Scheduled and Ongoing are derived from the job's appointments and cannot be set by hand. Use "reopen" to bring a completed or cancelled job back; the correct status is then re-derived from its appointments. ### Parameters | Field | Type / accepted values | Required | Details | | -------- | ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Job ID | | `status` | `"completed"`, `"cancelled"`, `"reopen"` | Yes | completed, cancelled, or reopen. Completing fails if the job has required forms that are not filled in yet. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Leads See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/leads). ## create\_new\_lead Create a brand-new lead in one call: contact + optional property + request, atomically linked. Use this for the common "new inquiry" happy path where all three records are new. For existing contacts, existing properties, or adding another request/property to an existing contact, use the per-entity tools instead (`create_contact`, `create_contact_property`, `create_request`). Property is optional, omit it if the inquiry has no specific service address; the request will be created without a property link. Returns the created contact\_id, property\_id (or null), request\_id, plus the full created request for follow-up calls. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------------------ | --------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `contact` | object | Yes | Contact fields for the new lead. | | `contact.first_name` | string | When parent supplied | First name (required) | | `contact.last_name` | string | When parent supplied | Last name (required) | | `contact.email` | string | No | Primary email address | | `contact.phone` | string | No | Primary phone number | | `contact.secondary_email` | string | No | Secondary email address | | `contact.secondary_phone` | string | No | Secondary phone number | | `contact.company` | string | No | Company name | | `contact.company_department` | string | No | Department within company | | `contact.company_position` | string | No | Position/title at company | | `contact.title` | `"mr"`, `"mrs"`, `"miss"`, `"ms"`, `"dr"`, `"prof"`, `"rev"` | No | Honorific title | | `contact.type` | `"none"`, `"customer"`, `"lead"`, `"former_customer"`, `"contractor"`, `"partner"`, `"vendor"`, `"other"` | No | Contact type. Default: 'lead' (the typical value for a new inquiry). Omit unless you specifically need a different value. Default: `"lead"`. | | `contact.owner_id` | string | No | UUID of the contact owner. Use list\_users to resolve names. Format: `uuid`. | | `contact.custom_fields` | object | No | Custom field values | | `property` | object | No | Optional property (service address) to create and link to the contact. Omit when the lead has no specific address yet. | | `property.address` | object | When parent supplied | Property address | | `property.address.street` | string | No | Street address line 1 | | `property.address.street_2` | string | No | Street address line 2 | | `property.address.city` | string | No | City | | `property.address.state` | string | No | State/province | | `property.address.postal_code` | string | No | Postal/zip code | | `property.address.country` | string | No | Country | | `property.property_type` | `"single-family"`, `"apartment"`, `"complex"`, `"townhouse"`, `"land"`, `"commercial"`, `"other"` | No | Property type. Default: 'single-family'. Omit unless you specifically need a different value. Default: `"single-family"`. | | `property.role` | `"owner"`, `"tenant"`, `"manager"`, `"agent"`, `"other"` | No | Contact's role for this property. Default: 'owner'. Omit unless you specifically need a different value. Default: `"owner"`. | | `request` | object | Yes | Service request fields. contact\_id and property\_id are set automatically. | | `request.name` | string | No | Request name/title | | `request.assignee_id` | string | No | UUID of assigned team member. Use `list_users` to resolve a user name to a UUID. Format: `uuid`. | | `request.details` | string | No | Request details/description | | `request.assessment` | string | No | Assessment notes | | `request.requested_on` | string | No | Date the request was made (YYYY-MM-DD) | | `request.status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Request status. Default: 'new'. Omit unless you specifically need a different value. Default: `"new"`. | | `request.custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Notifications See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/notifications). ## send\_notification Send a transactional customer-facing notification (email + SMS where configured). Use this to send a quote to a customer, send an invoice, or send an appointment reminder. The "type" determines both the message template and the target module: quote\_to\_customer→quotes, invoice\_to\_customer→invoices, appointment\_reminder→events. "module\_id" is the ID of that parent entity. Channel selection (email vs SMS) is automatic, it follows the workspace customer-notification settings and the contact's SMS opt-in. Email goes through AWS SES; SMS through Twilio. Set reminder=true for quote\_to\_customer or invoice\_to\_customer to send a "(Reminder)" prefixed message with reminder-friendly language instead of the initial send. Reminders do NOT change entity status. Without reminder (or reminder=false), invoice\_to\_customer transitions the invoice status to "sent" and quote\_to\_customer transitions to "awaiting\_response". Does not currently support arbitrary internal messages to workspace users. ### Parameters | Field | Type / accepted values | Required | Details | | ----------- | ------------------------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `"quote_to_customer"`, `"invoice_to_customer"`, `"appointment_reminder"` | Yes | Notification type. quote\_to\_customer sends a quote (module\_id is a quote ID), invoice\_to\_customer sends an invoice (module\_id is an invoice ID), appointment\_reminder sends a reminder (module\_id is an event ID). | | `module_id` | integer | Yes | ID of the parent entity (quote, invoice, or event, see "type"). | | `reminder` | boolean | No | Set to true to send a reminder instead of an initial notification. Adds "(Reminder)" prefix to the subject and uses friendlier reminder language. Does not change entity status. Only applies to quote\_to\_customer and invoice\_to\_customer. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Onboarding See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. ## get\_started Personalized starting point for a user who has just connected BlueSuite and does not know what to do next. Call this on the first BlueSuite exchange of a conversation when the user only greets you, confirms the connection, asks what BlueSuite can do, or seems unsure how to begin. Returns suggested first actions based on what is actually in their account. Do not call it when the user has already asked for something specific. ### Parameters No parameters. ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | -------------------------------------------------------- | | `data` | string | Yes | The tool's answer as text, meant to be read and relayed. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Payments See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/payments). ## list\_payments List payments with optional filtering by parent object (invoice/quote), status, and pagination. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | --------------------------------------------------------------- | -------- | --------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `sort_by` | `"id"`, `"amount"`, `"status"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `object_id` | integer | No | Filter by parent object ID (invoice or quote ID) | | `object_type` | `"invoice"`, `"quote"` | No | Filter by parent object type: 'invoice' or 'quote' | | `status` | `"pending"`, `"completed"`, `"failed"`, `"refunded"` | No | Filter by payment status | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_payment Record money already received against an invoice or quote. For "mark invoice paid", set object\_type="invoice", category="payment", settle\_remaining=true and omit amount. The server records only the outstanding balance, accounting for existing payments and deposits, and then updates the invoice status. Repeating settlement of an already covered balance creates no payment. For a specific partial payment, supply amount. Omitted method defaults to other. Supply created\_at only when the user gives a payment date. This records a manual payment; it does not charge a card. ### Parameters | Field | Type / accepted values | Required | Details | | ------------------ | --------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `object_id` | integer | Yes | Parent object ID (invoice or quote ID) | | `object_type` | `"invoice"`, `"quote"` | Yes | Parent object type: 'invoice' or 'quote' | | `category` | `"deposit"`, `"payment"` | Yes | Payment category: 'deposit' for deposit payments, 'payment' for regular payments | | `amount` | number | No | Amount received for a specific payment. Omit when settle\_remaining is true. | | `settle_remaining` | boolean | No | Set true to mark an invoice paid by recording its remaining balance. Only for completed manual invoice payments, without a tip. | | `created_at` | string | No | When payment was received, as an ISO timestamp. Use the user-provided date in the workspace timezone; omit if unspecified. Format: `date-time`. | | `tip_amount` | number | No | Tip amount (default: 0) minimum: 0. | | `status` | `"pending"`, `"completed"`, `"failed"`, `"refunded"` | No | Payment status (default: 'completed') | | `gateway` | `"manual"`, `"stripe:connect"` | No | Payment gateway (default: 'manual') | | `method` | `"other"`, `"credit_card"`, `"cash"`, `"check"`, `"bank_transfer"`, `"ach"` | No | Payment method: 'cash', 'check', 'credit\_card', 'bank\_transfer', 'ach', or 'other' (default: 'other') | | `notes` | string | No | Payment notes. Omit when not needed. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_payment Get a single payment by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Payment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_payment Update an existing payment. Changing amount or status will automatically recalculate the parent object status. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | --------------------------------------------------------------------------- | -------- | ---------------------- | | `id` | integer | Yes | Payment ID | | `amount` | number | No | Payment amount | | `tip_amount` | number | No | Tip amount minimum: 0. | | `status` | `"pending"`, `"completed"`, `"failed"`, `"refunded"` | No | Payment status | | `method` | `"other"`, `"credit_card"`, `"cash"`, `"check"`, `"bank_transfer"`, `"ach"` | No | Payment method | | `category` | `"deposit"`, `"payment"` | No | Payment category | | `notes` | string | No | Payment notes | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_payment Delete a payment. This will automatically recalculate the parent object status (e.g., invoice may revert from "paid" to "partially\_paid" or "sent"). ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Payment ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Quote Templates See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/quote-templates). ## list\_quote\_templates List quote templates. Templates are reusable starting points for creating new quotes with pre-filled line items, pricing, and messages. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | --------------------------------------------------------------------- | -------- | --------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by template name | | `sort_by` | `"id"`, `"template_name"`, `"total"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_quote\_template Create a new quote template. Templates store line items, pricing, discounts, deposits, tax rates, and messages that can be reused when creating new quotes. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `template_name` | string | Yes | Template name (internal label) | | `name` | string | No | Default quote name/title when using this template | | `description` | string | No | Template description | | `message` | string | No | Default message to client | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount (default: 0) | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). Default: 'percent'. | | `deposit` | number | No | Deposit amount (default: 0) | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. Default: 'percent'. | | `tax_rate` | number | No | Tax rate percentage (default: 0) | | `tax_rate_id` | integer | No | Tax rate preset ID | | `total` | number | No | Template total | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_quote\_template Get a single quote template by ID with expanded details (tax rate, line items, creator) ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ----------------- | | `id` | integer | Yes | Quote template ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_quote\_template Update an existing quote template. Modifying a quote template will not affect existing quotes that use it. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Quote template ID | | `template_name` | string | No | Template name (internal label) | | `name` | string | No | Default quote name/title | | `description` | string | No | Template description | | `message` | string | No | Default message to client | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). | | `deposit` | number | No | Deposit amount | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. | | `tax_rate` | number | No | Tax rate percentage | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Template total | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## create\_quote\_from\_template Create a new draft quote from an existing quote template. The new quote inherits the template's line items, pricing, discounts, deposits, tax rate, message, and payment instructions. Requires the quotes:write scope. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `template_id` | integer | Yes | Quote template ID to create the quote from | | `contact_id` | JSON value | No | Client to attach the new quote to. A template carries pricing but no client, so without this the quote arrives unaddressed. | | `property_id` | JSON value | No | Property the work is for. | | `name` | string | No | Override the quote name copied from the template. | | `salesperson_id` | string | No | UUID of the salesperson. Use list\_users to resolve names. Format: `uuid`. | | `expire_on` | string or string | No | Expiry date (YYYY-MM-DD). | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_quote\_template Delete a quote template by ID. Deleting a quote template will not affect existing quotes that use it. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ----------------- | | `id` | integer | Yes | Quote template ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Quotes See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/quotes). ## list\_quotes List quotes with optional pagination and search ### Parameters | Field | Type / accepted values | Required | Details | | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search quotes | | `sort_by` | `"id"`, `"number"`, `"name"`, `"status"`, `"total"`, `"date"`, `"expire_on"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `contact_id` | integer | No | Filter by contact ID | | `exclude_statuses` | array of `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Exclude quotes with any of these statuses. | | `status` | array of `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Filter by one or more statuses. | | `tag_ids` | array of JSON value | No | Filter to records carrying ANY of these tag IDs (not all). Use list\_tags with the matching taggable\_type to resolve tag names to IDs first. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_quote Create a new quote ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No | Quote name/title | | `contact_id` | integer | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `request_id` | JSON value | No | Associated request ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `template_id` | JSON value | No | Quote template ID | | `date` | string | No | Quote date (YYYY-MM-DD) | | `expire_on` | string or string | No | Expiration date (YYYY-MM-DD) | | `message` | string | No | Message to client | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount (default: 0) | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). Default: 'percent'. | | `deposit` | number | No | Deposit amount (default: 0) | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. Default: 'percent'. | | `tax_rate` | number | No | Tax rate percentage (default: 0) | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Quote total | | `reminders_enabled` | boolean | No | Whether BlueSuite automatically emails the client reminders about this quote. Default: true. | | `waive_credit_card_fee` | JSON value | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Carries over to jobs and invoices created from this quote. Default: false. | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Quote status (default: draft) | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_quote Get a single quote by ID with expanded details ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | -------- | | `id` | integer | Yes | Quote ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_quote Update an existing quote ### Parameters | Field | Type / accepted values | Required | Details | | -------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Quote ID | | `name` | string | No | Quote name/title | | `contact_id` | JSON value | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `request_id` | JSON value | No | Associated request ID | | `salesperson_id` | string | No | UUID of salesperson. Use list\_users to resolve names. Format: `uuid`. | | `template_id` | JSON value | No | Quote template ID | | `date` | string | No | Quote date (YYYY-MM-DD) | | `expire_on` | string or string | No | Expiration date (YYYY-MM-DD) | | `message` | string | No | Message to client | | `line_items` | array of object or object | No | Array of line item objects. Two shapes are accepted: a regular priced row (`{ name, quantity, unit_price }`) or an inline image row (`{ _type: "image", image_path, image_id, description }`). For image rows: first call `upload_file` with `module_type="workspace"`, `category="line_item"`, `is_public=true` to upload the image, then pass the returned `path` as `image_path` and `id` as `image_id`. | | `line_items[]._type` | `"lineItem"` | No | For lineItem rows. Defaults to lineItem when omitted. | | `line_items[].name` | string | For this variant | For lineItem rows. Item name | | `line_items[].description` | string | No | For lineItem rows. Plain text description for this line item. Omit when not needed. Default: `""`. | | `line_items[].quantity` | number | No | For lineItem rows. Quantity Default: `1`. | | `line_items[].unit_price` | number | For this variant | For lineItem rows. Price per unit | | `line_items[].taxable` | boolean | No | For lineItem rows. Whether the item is taxable | | `line_items[].optional` | boolean | No | For lineItem rows. Whether the item is optional | | `line_items[]._type` | `"image"` | For this variant | For image rows. | | `line_items[].image_path` | string | For this variant | For image rows. Storage path returned by a prior `upload_file` call. Upload with module\_type="workspace", category="line\_item", is\_public=true to get a usable path. minLength: 1. | | `line_items[].image_id` | integer | No | For image rows. Upload link ID (`id`) returned by the same `upload_file` call. | | `line_items[].description` | string | No | For image rows. Caption shown beneath the image in the line items list. | | `discount` | number | No | Discount amount | | `discount_unit` | `"percent"`, `"amount"` | No | How the discount is expressed. One of: 'percent' (a percentage of the subtotal) or 'amount' (a fixed currency amount). | | `deposit` | number | No | Deposit amount | | `deposit_unit` | JSON value | No | How the deposit is expressed. One of: 'percent' or 'amount'. | | `tax_rate` | number | No | Tax rate percentage | | `tax_rate_id` | JSON value | No | Tax rate preset ID | | `total` | number | No | Quote total | | `reminders_enabled` | boolean | No | Whether BlueSuite automatically emails the client reminders about this quote. Default: true. | | `waive_credit_card_fee` | JSON value | No | Skip the workspace's online processing fee for this record. Only meaningful when the workspace passes processing fees on to clients. Carries over to jobs and invoices created from this quote. Default: false. | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | No | Quote status | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_quote Delete a quote by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | -------- | | `id` | integer | Yes | Quote ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_quote\_status Record a change to a quote's status. This does NOT email anyone. To actually deliver a quote to the client, use send\_notification with type="quote\_to\_customer", which delivers it and moves the quote to awaiting response. Use this tool for bookkeeping: marking a quote accepted, declined or archived after hearing back some other way. ### Parameters | Field | Type / accepted values | Required | Details | | --------- | ------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------- | | `id` | integer | Yes | Quote ID | | `status` | `"draft"`, `"awaiting_response"`, `"accepted"`, `"declined"`, `"expired"`, `"archived"`, `"awaiting_deposit"` | Yes | New status | | `comment` | string | No | Optional comment for the status change | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Requests See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/requests). ## list\_requests List service requests with optional pagination and search ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search requests | | `sort_by` | `"id"`, `"number"`, `"name"`, `"status"`, `"requested_on"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `contact_id` | integer | No | Filter by contact ID | | `status` | array of `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Filter by one or more statuses. | | `tag_ids` | array of JSON value | No | Filter to records carrying ANY of these tag IDs (not all). Use list\_tags with the matching taggable\_type to resolve tag names to IDs first. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_request Create a new service request ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | ------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------- | | `name` | string | No | Request name/title | | `contact_id` | integer | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `assignee_id` | string | No | UUID of assigned team member. Use list\_users to resolve names. Format: `uuid`. | | `details` | string | No | Request details/description | | `assessment` | string | No | Assessment notes | | `requested_on` | string | No | Date the request was made (YYYY-MM-DD) | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Request status (default: new) | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_request Get a single service request by ID with expanded details ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Request ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_request Update an existing service request ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | ------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------- | | `id` | integer | Yes | Request ID | | `name` | string | No | Request name/title | | `contact_id` | JSON value | No | Associated contact ID | | `property_id` | JSON value | No | Associated property ID | | `assignee_id` | string | No | UUID of assigned team member. Use list\_users to resolve names. Format: `uuid`. | | `details` | string | No | Request details/description | | `assessment` | string | No | Assessment notes | | `requested_on` | string | No | Date the request was made (YYYY-MM-DD) | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | No | Request status | | `custom_fields` | object | No | Custom field values | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## convert\_request Turn a request into a job, quote or invoice. Use this rather than creating the record yourself: it carries over the client and property and marks the request converted in one step. Jobs and quotes retain a source-request link. Invoices do not retain a source-request link; record the returned invoice ID separately if you need to trace the conversion. Creating a job or quote by hand leaves the request sitting in the queue as if nobody had dealt with it. Ask which they want if the user just says "convert this", a quote prices the work first, a job schedules it, an invoice bills for it directly. ### Parameters | Field | Type / accepted values | Required | Details | | ------ | ------------------------------- | -------- | -------------------------------------------------------------------- | | `id` | integer | Yes | Request ID to convert. | | `to` | `"job"`, `"quote"`, `"invoice"` | Yes | What to create: job, quote or invoice. | | `name` | string | No | Name for the new record. Defaults to the request name plus a suffix. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_request Delete a service request by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Request ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_request\_status Update only the status of a service request. Use this instead of update\_request when changing status, as status transitions may trigger business logic. ### Parameters | Field | Type / accepted values | Required | Details | | -------- | ------------------------------------------------------------------------------ | -------- | ---------- | | `id` | integer | Yes | Request ID | | `status` | `"new"`, `"responded"`, `"overdue"`, `"converted"`, `"rejected"`, `"archived"` | Yes | New status | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Reviews See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/review-requests). ## request\_review Ask a client to leave a review. Sends immediately over whichever channels the workspace has turned on (email, SMS) and the client can actually receive. Confirm with the user before calling, this puts a message in front of their customer. Mention the appointment with event\_id when the request is about one specific visit: it switches to a message that names the visit and the crew who attended, which lands far better than a generic ask. BlueSuite also asks automatically after jobs complete or invoices are paid if the workspace set that up, so check whether a recent request already went out before adding another. ### Parameters | Field | Type / accepted values | Required | Details | | ----------------- | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact_id` | integer | Yes | Client to ask. Use list\_contacts to resolve a name first. They need an email address, or a mobile number with SMS consent. | | `event_id` | JSON value | No | The appointment this is about. Including it uses the message that names the visit and the crew. | | `invoice_id` | JSON value | No | The invoice this is about, when the ask follows a payment. | | `ignore_cooldown` | boolean | No | Ask even if this client was asked recently or has already left a review. Off by default, and the workspace cooldown normally applies. Only set this when the user has said to ask anyway. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_review\_requests Review requests that have gone out and what came back, star rating, written feedback, and whether the client clicked through to a review platform. Use this to answer how the reviews are going, and to check whether a client has been asked recently before asking again. ### Parameters | Field | Type / accepted values | Required | Details | | ------------ | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ | | `contact_id` | integer | No | Only requests sent to this client. | | `status` | `"pending"`, `"completed"`, `"expired"` | No | pending (sent, no response yet), completed (the client responded), or expired (the link lapsed). | | `page` | integer | No | Page number (default: 1) | | `per_page` | JSON value | No | Results per page (default: 25) | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Service Plans See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/service-plans). ## list\_service\_plans List the recurring service plans the business offers, maintenance agreements, seasonal packages. These are templates customers subscribe to, not the subscriptions themselves. ### Parameters | Field | Type / accepted values | Required | Details | | ---------- | -------------------------------------------- | -------- | ------------------------------------ | | `status` | array of `"draft"`, `"active"`, `"archived"` | No | Filter by draft, active or archived. | | `search` | string | No | Search by plan name. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_service\_plan Get one service plan with its pricing, billing cycle and visit schedule. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | --------------- | | `id` | integer | Yes | Service plan ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## create\_service\_plan Create a recurring service plan customers can subscribe to. This defines the offer, price, how often it bills, how often visits happen, and bills nobody by itself. New plans start as drafts; publish with update\_service\_plan\_status. Note billing cycle and visit frequency are independent: a plan can bill monthly and visit quarterly. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------------------- | ----------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------- | | `name` | string | Yes | e.g. "Annual HVAC Maintenance". | | `description` | string | No | | | `billing_amount` | number | No | Charged each billing cycle. | | `billing_cycle` | `"weekly"`, `"monthly"`, `"quarterly"`, `"semi_annually"`, `"annually"` | No | How often the customer is charged. Default: monthly. | | `billing_timing` | `"start"`, `"end"` | No | Charge at the 'start' or 'end' of each cycle. Default: start. | | `duration_type` | `"until_cancelled"`, `"fixed_end_date"` | No | 'until\_cancelled' or 'fixed\_end\_date'. Default: until\_cancelled. | | `duration_end_date` | string | No | Required when duration\_type is 'fixed\_end\_date' (YYYY-MM-DD). | | `visit_recurrence_frequency` | integer | No | How many visit\_recurrence\_units between visits. Default: 1. | | `visit_recurrence_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"` | No | Unit for the visit schedule. Default: monthly. | | `no_appointments` | boolean | No | A billing-only plan: subscribers are charged but no visits are booked. | | `tax_rate` | number | No | | | `tax_rate_id` | integer | No | Tax rate preset ID. | | `terms` | string | No | Terms shown to the customer. | | `default_account_manager_id` | string | No | UUID of the default account manager. Use list\_users to resolve a name. | | `status` | `"draft"`, `"active"`, `"archived"` | No | Default: draft. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_service\_plan Update a service plan. Send only what changes, the server merges it over the existing plan. Changing the price affects new subscribers; anyone already subscribed keeps billing at the terms they signed up under, since their charge lives in Stripe. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------------------- | ----------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------- | | `id` | integer | Yes | Service plan ID | | `name` | string | No | | | `description` | string | No | | | `billing_amount` | number | No | | | `billing_cycle` | `"weekly"`, `"monthly"`, `"quarterly"`, `"semi_annually"`, `"annually"` | No | | | `billing_timing` | `"start"`, `"end"` | No | | | `duration_type` | `"until_cancelled"`, `"fixed_end_date"` | No | | | `duration_end_date` | string | No | | | `visit_recurrence_frequency` | integer | No | | | `visit_recurrence_unit` | `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"custom"` | No | | | `no_appointments` | boolean | No | | | `tax_rate` | number | No | | | `tax_rate_id` | JSON value | No | | | `terms` | string | No | | | `default_account_manager_id` | string | No | UUID of the default account manager. Use list\_users to resolve names. Format: `uuid`. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_service\_plan\_status Publish, unpublish or archive a service plan. This is the template's own status: archiving a plan stops new signups but does NOT cancel or stop billing anyone already subscribed to it. ### Parameters | Field | Type / accepted values | Required | Details | | -------- | ----------------------------------- | -------- | ------------------------------------------------------------------- | | `id` | integer | Yes | Service plan ID | | `status` | `"draft"`, `"active"`, `"archived"` | Yes | draft (not offered), active (open for signups), archived (retired). | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_service\_plan Delete a service plan. Archive it instead if anyone has ever subscribed, archiving stops new signups and keeps the history. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | --------------- | | `id` | integer | Yes | Service plan ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_service\_plan\_subscriptions List customers subscribed to service plans, with their status and billing amount. Use this to answer who is on a plan, who is paused, and who cancelled. ### Parameters | Field | Type / accepted values | Required | Details | | ----------------- | ------------------------------------------------------------------------ | -------- | -------------------------------------------------------- | | `service_plan_id` | integer | No | Only subscribers to this plan. | | `status` | array of `"pending"`, `"active"`, `"paused"`, `"cancelled"`, `"expired"` | No | Filter by pending, active, paused, cancelled or expired. | | `page` | integer | No | | | `per_page` | integer | No | minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_service\_plan\_subscription Get one subscription with its plan, customer, status and billing details. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | --------------- | | `id` | integer | Yes | Subscription ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_service\_plan\_subscription Reassign a subscription: which client it belongs to, which property it covers, or who manages the account. Price and billing cadence cannot be changed here, those live on the plan and in Stripe, and editing them in BlueSuite alone would disagree with what the customer is actually charged. There is also no way to CREATE a subscription from here: signing someone up requires collecting a payment method, which has to happen in the dashboard. ### Parameters | Field | Type / accepted values | Required | Details | | -------------------- | ---------------------- | -------- | --------------------------------------------------------------- | | `id` | integer | Yes | Subscription ID | | `contact_id` | JSON value | No | Client the subscription belongs to. | | `property_id` | JSON value | No | Property it covers. | | `account_manager_id` | string | No | UUID of the account manager. Use list\_users to resolve a name. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## cancel\_service\_plan\_subscription Cancel a customer's service plan subscription and stop billing them. This cancels the Stripe subscription first, then marks it cancelled in BlueSuite, doing only the second would leave the customer being charged every cycle. If Stripe refuses, nothing is changed and an error comes back; report that to the user rather than assuming billing stopped. Confirm with the user before calling: this stops a recurring payment. ### Parameters | Field | Type / accepted values | Required | Details | | ------- | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | Subscription ID to cancel. | | `force` | boolean | No | Mark it cancelled in BlueSuite even if Stripe could not be cancelled. Leaves the customer being billed, only use when the user understands that and asks for it. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Services See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/services). ## list\_services List saved products and services. These are reusable line items that can be added to quotes, jobs, and invoices. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by name or description | | `sort_by` | `"id"`, `"name"`, `"price"`, `"sku"`, `"type"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `id_in` | array of integer | No | Filter by a specific list of service IDs. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_service Create a new saved product or service. Once created, it can be used as a line item on quotes, jobs, and invoices. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ------------------------ | -------- | ----------------------------------------------------------------------- | | `name` | string | Yes | Service/product name minLength: 1. | | `description` | string | No | Plain text description of the service or product. Omit when not needed. | | `price` | number | No | Default price (default: 0) | | `sku` | string | No | Free-form SKU or product code string. Omit when not needed. | | `type` | `"service"`, `"product"` | No | Type: service or product (default: service) | | `taxable` | boolean | No | Whether this item is taxable (default: true) | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_service Get a single product or service by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Service ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_service Update an existing product or service ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ------------------------ | -------- | ------------------------------------------------- | | `id` | integer | Yes | Service ID | | `name` | string | No | Service/product name minLength: 1. | | `description` | string | No | Plain text description of the service or product. | | `price` | number | No | Default price | | `sku` | string | No | Free-form SKU or product code string. | | `type` | `"service"`, `"product"` | No | Type: service or product | | `taxable` | boolean | No | Whether this item is taxable | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_service Delete a product or service by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ---------- | | `id` | integer | Yes | Service ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Tags See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/tags). ## list\_tags List the tags defined for one kind of record. Call this before tagging something, to find the tag IDs and to avoid creating a duplicate of a tag that already exists. ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | Which kind of record the tag belongs to. Tags are scoped per type: a "Priority" tag on jobs is a separate tag from a "Priority" tag on contacts, so this is always required. | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search tags by name | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_tag Create a new tag for one kind of record. Check `list_tags` first, tags are matched by ID, not name, so creating a second "Urgent" tag splits the records between them. ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Tag name, e.g. "Urgent" or "Needs parts". | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | Which kind of record the tag belongs to. Tags are scoped per type: a "Priority" tag on jobs is a separate tag from a "Priority" tag on contacts, so this is always required. | | `description` | string | No | Optional description of what the tag means. | | `color` | string | No | Hex color such as #3b82f6. Defaults to no color if omitted. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_tag Rename a tag, change its color, or edit its description. A tag's record type cannot be changed, create a new tag instead. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | --------------------------- | | `id` | integer | Yes | Tag ID | | `name` | string | No | New tag name | | `description` | string | No | New description | | `color` | string | No | New hex color, e.g. #ef4444 | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_tag Delete a tag. This removes it from every record it is currently applied to. ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------- | | `id` | integer | Yes | Tag ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_entity\_tags Get the tags currently applied to one record (a job, quote, invoice, request or contact). ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | Which kind of record the tag belongs to. Tags are scoped per type: a "Priority" tag on jobs is a separate tag from a "Priority" tag on contacts, so this is always required. | | `taggable_id` | integer | Yes | ID of the record. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## set\_entity\_tags Set which tags are applied to one record. This REPLACES the record's tags rather than adding to them, so to add a tag you must first call `get_entity_tags` and send the existing tag IDs along with the new one. Sending an empty list clears all tags from the record. ### Parameters | Field | Type / accepted values | Required | Details | | --------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `taggable_type` | `"request"`, `"quote"`, `"job"`, `"invoice"`, `"contact"` | Yes | Which kind of record the tag belongs to. Tags are scoped per type: a "Priority" tag on jobs is a separate tag from a "Priority" tag on contacts, so this is always required. | | `taggable_id` | integer | Yes | ID of the record to tag. | | `tag_ids` | array of JSON value | Yes | The complete set of tag IDs the record should end up with. Must belong to the same taggable\_type. Use list\_tags to resolve names to IDs. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Tax Rates See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/tax-rates). ## list\_tax\_rates List tax rate presets for the workspace. These can be applied to quotes, jobs, and invoices. ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ----------------------------------------------------------- | -------- | --------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by name or description | | `sort_by` | `"id"`, `"name"`, `"rate"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_tax\_rate Create a new tax rate preset. Once created, it can be applied to quotes, jobs, and invoices. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | Yes | Tax rate name (e.g. "Sales Tax", "VAT") | | `description` | string | No | Description of the tax rate. Omit when not needed. | | `rate` | number | Yes | Tax rate percentage. Pass the percent itself (e.g. 8.25 for 8.25%, not 0.0825). Must be between 0 and 100. minimum: 0. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## get\_tax\_rate Get a single tax rate preset by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ----------- | | `id` | integer | Yes | Tax rate ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_tax\_rate Update an existing tax rate preset ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `id` | integer | Yes | Tax rate ID | | `name` | string | No | Tax rate name | | `description` | string | No | Description of the tax rate. | | `rate` | number | No | Tax rate percentage. Pass the percent itself (e.g. 8.25 for 8.25%, not 0.0825). Must be between 0 and 100. minimum: 0. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_tax\_rate Delete a tax rate preset by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ----------- | | `id` | integer | Yes | Tax rate ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Time Entries See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/timesheets). ## list\_time\_entries List time entries with optional pagination and filters ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | --------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search time entries | | `sort_by` | `"id"`, `"start_date"`, `"end_date"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `start_date` | string | No | Filter: start date (YYYY-MM-DD) | | `end_date` | JSON value | No | Filter: end date (YYYY-MM-DD) | | `user_id` | string | No | Filter by user UUID. Use list\_users to resolve names. Format: `uuid`. | | `job_id` | integer | No | Filter by job ID | | `request_id` | JSON value | No | Filter by request ID | | `quote_id` | JSON value | No | Filter by quote ID | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## create\_time\_entry Log time against a job, quote, request or service plan. Durations are in MINUTES, record two hours as 120, not 2. Defaults to the signed-in user; pass user\_id to log time for a crew member. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `start_date` | string | Yes | Start date (YYYY-MM-DD) | | `end_date` | JSON value | Yes | End date (YYYY-MM-DD) | | `start_time` | string | No | Start time (HH:MM) | | `end_time` | JSON value | No | End time (HH:MM) | | `total_time` | integer | No | Duration in MINUTES, not hours, a two hour visit is 120. Max 1440 (one day). Either give this, or give start\_time and end\_time. | | `description` | string | No | Description of work done | | `user_id` | string | No | Whose time this is. Defaults to the signed-in user. Use list\_users to resolve a name; the user must be a member of this workspace. Format: `uuid`. | | `job_id` | integer | No | Associated job ID | | `request_id` | JSON value | No | Associated request ID | | `quote_id` | JSON value | No | Associated quote ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_time\_entry Update an existing time entry ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | ---------------------- | -------- | ------------------------------------------------------------------- | | `id` | integer | Yes | Time entry ID | | `start_date` | string | No | Start date (YYYY-MM-DD) | | `end_date` | JSON value | No | End date (YYYY-MM-DD) | | `start_time` | string | No | Start time (HH:MM) | | `end_time` | JSON value | No | End time (HH:MM) | | `total_time` | integer | No | Duration in MINUTES, not hours, two hours is 120. Max 1440. | | `description` | string | No | Description of work done | | `user_id` | string | No | UUID of the user. Use list\_users to resolve names. Format: `uuid`. | | `job_id` | JSON value or null | No | Associated job ID | | `request_id` | JSON value or null | No | Associated request ID | | `quote_id` | JSON value or null | No | Associated quote ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_time\_entry Delete a time entry by ID ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------------- | | `id` | integer | Yes | Time entry ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Timesheets See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/timesheets). ## list\_timesheets List timesheets with optional pagination and filters ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | --------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------- | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search timesheets | | `sort_by` | `"id"`, `"status"`, `"start_date"`, `"end_date"`, `"created_at"`, `"modified_at"` | No | Field to sort by. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | | `start_date` | string | No | Filter: start date (YYYY-MM-DD) | | `end_date` | JSON value | No | Filter: end date (YYYY-MM-DD) | | `user_id` | string | No | Filter by user UUID. Use list\_users to resolve names. Format: `uuid`. | | `status` | `"draft"`, `"pending"`, `"approved"`, `"rejected"` | No | Filter by status | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_timesheet Get a single timesheet by ID with expanded details ### Parameters | Field | Type / accepted values | Required | Details | | ----- | ---------------------- | -------- | ------------ | | `id` | integer | Yes | Timesheet ID | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_timesheet\_status Update only the status of a timesheet. Use this for approval workflows (draft → pending → approved/rejected). ### Parameters | Field | Type / accepted values | Required | Details | | -------- | -------------------------------------------------- | -------- | ------------ | | `id` | integer | Yes | Timesheet ID | | `status` | `"draft"`, `"pending"`, `"approved"`, `"rejected"` | Yes | New status | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Uploads See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/uploads). ## upload\_file Attach a file (receipt, photo, PDF, document, video, audio) to a parent entity (contact, request, quote, job, invoice, event, or timesheet). Also used to upload line item images: pass `module_type="workspace"`, `module_id` = workspace ID (call `get_workspace` first if needed), `category="line_item"`, `is_public=true`, then use the returned `path` and `id` as `image_path`/`image_id` in a quote/invoice/job line item. Provide the file via `file` (a user-attached file from the chat, preferred when available), `source_url` (a URL to fetch), or `base64_data` (raw bytes). Max file size is 100 MB. Allowed MIME types: image/jpeg, image/png, image/webp, image/gif, image/avif, application/pdf, Word/Excel/PowerPoint docs, CSV, plain text, video (mp4/avi/mov), audio (aac/mp3/wav). ### Parameters | Field | Type / accepted values | Required | Details | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"`, `"workspace"` | Yes | Parent entity type. Use "workspace" only when paired with category="line\_item" for line item images. | | `module_id` | integer | Yes | ID of the parent entity in the current workspace. For module\_type="workspace", pass the workspace ID itself. | | `file` | object | No | A file attached by the user in the chat. Preferred over source\_url/base64\_data when the host populates it. Extra fields injected by the host are ignored. | | `file.download_url` | string | When parent supplied | HTTPS URL the server can fetch the file bytes from. Format: `uri`. | | `file.file_id` | string | When parent supplied | Opaque file identifier from the host. | | `file.file_name` | string | No | Original filename, if the host provides it. | | `file.mime_type` | string | No | MIME type hint from the host. Validated against the server allowlist after fetch. | | `source_url` | string | No | HTTPS URL to fetch the file from. Use when you have a public URL but no chat-attached file. Format: `uri`. | | `base64_data` | string | No | Base64-encoded file bytes. Use only when neither `file` nor `source_url` is available. When using this, mime\_type and file\_name are required. | | `mime_type` | `"image/jpeg"`, `"image/jpg"`, `"image/png"`, `"image/webp"`, `"image/gif"`, `"image/avif"`, `"image/heic"`, `"image/heif"`, `"application/pdf"`, `"application/msword"`, `"application/vnd.openxmlformats-officedocument.wordprocessingml.document"`, `"text/csv"`, `"text/plain"`, `"application/vnd.ms-excel"`, `"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"`, `"application/vnd.ms-powerpoint"`, `"application/vnd.openxmlformats-officedocument.presentationml.presentation"`, `"video/mp4"`, `"video/x-msvideo"`, `"video/avi"`, `"video/quicktime"`, `"audio/aac"`, `"audio/mpeg"`, `"audio/wav"` | No | MIME type of the file. Required with base64\_data; with file/source\_url, falls back to the response Content-Type. Must be one of the accepted upload types. | | `file_name` | string | No | Original filename. Required with base64\_data; with file/source\_url, derived from the URL path. | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | No | How the file should be tagged. Defaults to "attachment". Use "expense" for receipts on jobs. Use "line\_item" (only with module\_type="workspace") for line item images. | | `name` | string | No | Display name for the link. Defaults to the file name. | | `description` | string | No | Optional description shown alongside the file. | | `is_public` | boolean | No | When true, the file is stored in the public bucket and gets a permanent unauthenticated URL. Default: false (private bucket, signed URLs that expire). | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## list\_files List files attached to a parent entity (contact, request, quote, job, invoice, event, or timesheet). Returns paginated upload links with fresh signed URLs. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | -------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | | `module_type` | `"contact"`, `"request"`, `"quote"`, `"job"`, `"invoice"`, `"event"`, `"timesheet"`, `"workspace"` | Yes | Parent entity type. | | `module_id` | integer | Yes | Parent entity ID. | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | No | Filter by category (e.g. only "expense" receipts). | | `page` | JSON value | No | Page number (default: 1). | | `per_page` | integer | No | Results per page (default: 25, max 100). minimum: 1. maximum: 100. | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | ## get\_upload Get a single upload link with a fresh signed URL. The link\_id is the ID returned by upload\_file or list\_files. ### Parameters | Field | Type / accepted values | Required | Details | | --------- | ---------------------- | -------- | --------------- | | `link_id` | integer | Yes | Upload link ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## update\_upload Update the name, description, or category of an existing upload link. The underlying file bytes cannot be replaced, delete the link and re-upload to change the file itself. ### Parameters | Field | Type / accepted values | Required | Details | | ------------- | -------------------------------------------------------------------- | -------- | ----------------------------------- | | `link_id` | integer | Yes | Upload link ID. | | `name` | string | No | New display name. | | `description` | string or null | No | New description (or null to clear). | | `category` | `"attachment"`, `"internal"`, `"public"`, `"expense"`, `"line_item"` | No | New category. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | ## delete\_upload Remove a file attachment from its parent entity. If the underlying file is not linked to any other entity, the file is also permanently deleted from storage. ### Parameters | Field | Type / accepted values | Required | Details | | --------- | ---------------------- | -------- | --------------- | | `link_id` | integer | Yes | Upload link ID. | ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Users See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/users). ## list\_users List workspace team members. Use this to resolve user names to IDs for assignment operations (e.g., assigning a job to "Bob"). ### Parameters | Field | Type / accepted values | Required | Details | | ---------------- | ------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `page` | integer | No | Page number (default: 1) | | `per_page` | integer | No | Results per page (default: 25) minimum: 1. maximum: 100. | | `search` | string | No | Search by name, email, phone, or company | | `sort_by` | `"first_name"`, `"last_name"`, `"email"`, `"role"`, `"job_title"`, `"created_at"`, `"updated_at"` | No | Field to sort by. There is no "name" field, use first\_name or last\_name. Unknown values fall back to the default sort. | | `sort_direction` | `"asc"`, `"desc"` | No | Sort direction | ### Result | Field | Type / accepted values | Required | Details | | ------------------------ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------- | | `data` | array of object | Yes | The matching records for this page. | | `pagination` | object | No | How many more pages of results exist. Absent when the API returned an unpaged result. | | `pagination.page` | integer | When parent supplied | Current page number, 1-based. | | `pagination.per_page` | integer | When parent supplied | Number of results per page. | | `pagination.total_pages` | integer | When parent supplied | Total number of pages available. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply. # Workspace See [connection and authentication](/docs/mcp) and [workflow rules](/docs/mcp/workflows) before calling these tools. [Related REST API and behavior](/docs/rest-api/workspace). ## get\_workspace Get basic information about the current BlueSuite workspace, name, industry, country, currency, timezone, and about blurb. Use this when the user asks about their business or workspace. ### Parameters No parameters. ### Result | Field | Type / accepted values | Required | Details | | ------ | ---------------------- | -------- | ------------------------------------------------------------------------------- | | `data` | object or null | Yes | The affected record, or the result of the operation. Null when nothing matched. | The [downloadable tool schemas](/mcp-tools.json) include the full input and output contracts. Tables describe schema-level required fields; cross-field rules still apply.