# Flex API — full reference Base URL: https://app.flexonthejob.com/api/v1. OpenAPI: https://app.flexonthejob.com/openapi/v1.json. Interactive docs: https://app.flexonthejob.com/docs. Flex's tenant API for integrations and AI agents. Every response is scoped to the organization that owns the presented key. **Authentication.** Send `Authorization: Bearer flx_live_…` on every request. Keys are created by an organization administrator inside Flex; the secret is shown once and stored hashed. A key belongs to exactly one organization. **Permissions.** A key is granted a subset of Flex permissions when it is created. Each endpoint states the permission it needs; a key without it receives 403. `GET /api/v1/modules` lists the permissions of the presented key. **Modules.** Endpoints that belong to an optional Flex module (scheduling, invoicing, …) answer 403 with a problem detail when the organization has not activated that module. `GET /api/v1/modules` shows which modules are active. **Rate limits.** Fixed one-minute windows, per key (default 120 requests/min, configurable per key) and per organization (600/min). Exceeding either returns 429 with a `Retry-After` header (seconds). **Pagination.** List endpoints return `{ "items": [...], "nextCursor": "…" }`. Pass `after=` to fetch the next page and `limit` to size it (1–200, default 50). `nextCursor` is null on the last page; cursors are opaque and stable. **ETags.** Single-resource GETs return a weak `ETag`. Send it back as `If-None-Match` to receive 304 Not Modified with no body when nothing changed. **Idempotency.** Send an `Idempotency-Key` header (any unique string, up to 128 characters) on POST, PUT, PATCH and DELETE. Replaying the same key with the same body returns the original response instead of repeating the write; the same key with a different body is rejected with 422. Keys are remembered for 24 hours per organization. **Errors.** Failures are RFC 7807 `application/problem+json`: 400 validation errors list per-field messages under `errors`; 401/403/404/409/422/429/500 carry `title`, `detail` and a `traceId` to quote in support requests. # Operations ## Customers ### GET /api/v1/customers Lists customers, oldest first, one page at a time. Cursor pagination: pass the `nextCursor` of the previous response as `after`. Filters combine with AND. Example: `GET /api/v1/customers?isActive=true&updatedSince=2026-01-01T00:00:00Z&limit=100`. Parameters: - after (query, string, optional): SyncId of the last item of the previous page; omit for the first page. - limit (query, integer (int32), optional): Page size, 1–200 (default 50). - updatedSince (query, string (date-time), optional): Only customers created or edited at/after this UTC instant (ISO-8601, e.g. `2026-01-01T00:00:00Z`). - isActive (query, boolean, optional): Only active (`true`) or deactivated (`false`) customers; omit for both. - customerType (query, CustomerType, optional): Only this type: `EndCustomer`, `Installer`, `Distributor` or `Commercial`. - status (query, CustomerStatus, optional): Only this status flag: `None`, `Preferred` or `Difficult`. - search (query, string, optional): Case-insensitive substring matched against name, company name and e-mail. Responses: - 200: A page of customers. — PagedResponseOfCustomerDto - items (CustomerDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 400: Malformed `after` cursor or filter value (validation problem listing the field). — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/customers Creates a customer. Requires `ManageCustomers` and an `Idempotency-Key` header (unique per attempt; a retry with the same key and body replays the first response). The customer is created active with a zero balance; `country` defaults to `"USA"`; when any of `address`/`city`/`state`/`postalCode` is given, a "Primary Address" mailing location is created too (see `/customers/{id}/locations`). The write is attributed to the user who created the API key. Request body (application/json): CustomerWriteRequest: Customer fields; only `name` is required. - name (string, required): Display name, e.g. `"Jane Doe"`. Required, ≤ 200 characters. - companyName (string, nullable): Company / trading name, e.g. `"Doe Roofing LLC"`. ≤ 200. - customerType (CustomerType) - primaryContact (string, nullable): Primary contact person, if different from `name`. ≤ 100. - email (string (email), nullable): Contact e-mail address. ≤ 256. - phone (string (tel), nullable): Main phone number, stored as entered. ≤ 20. - mobilePhone (string (tel), nullable): Mobile phone number, stored as entered. ≤ 20. - address (string, nullable): Primary (billing) street address line 1. ≤ 200. On create, any of address/city/state/postalCode also creates a "Primary Address" location. - address2 (string, nullable): Primary address line 2 (suite, unit). ≤ 200. - city (string, nullable): Primary address city. ≤ 100. - state (string, nullable): Primary address state / province. ≤ 50. - postalCode (string, nullable): Primary address ZIP / postal code. ≤ 20. - country (string, nullable): Primary address country. ≤ 100. Defaults to `"USA"` on create. - taxId (string, nullable): Tax identification number. ≤ 50. - isTaxExempt (boolean, nullable): True when tax exempt; null when not recorded. - priceTier (PriceTier) - paymentTerms (PaymentTerms) - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. Default 0. - isActive (boolean): Update only — false deactivates the customer (kept for history, hidden from pickers). Ignored on create; new customers are always active. - status (CustomerStatus) - warningNotes (string, nullable): Warning shown to staff when the customer is selected. ≤ 1000. - notes (string, nullable): Free-form internal notes. Responses: - 201: The new customer; `Location` points at `/api/v1/customers/{id}`. — CustomerDto - id (integer (int32)): Numeric id used in URLs (e.g. `/api/v1/customers/42`). Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor and when correlating with offline devices. - name (string): Display name, e.g. `"Jane Doe"`. Always present. - companyName (string, nullable): Company / trading name when the customer is a business, e.g. `"Doe Roofing LLC"`. - customerType (string): Customer type: `EndCustomer`, `Installer`, `Distributor` or `Commercial`. - primaryContact (string, nullable): Name of the primary contact person, if different from `name`. - email (string, nullable): Contact e-mail address. - phone (string, nullable): Main phone number as entered (no normalisation). - mobilePhone (string, nullable): Mobile phone number as entered. - address (string, nullable): Primary (billing) street address line 1. - address2 (string, nullable): Primary address line 2 (suite, unit). - city (string, nullable): Primary address city. - state (string, nullable): Primary address state / province. - postalCode (string, nullable): Primary address ZIP / postal code. - country (string, nullable): Primary address country. - isTaxExempt (boolean, nullable): True when the customer is tax exempt; null when not recorded. - priceTier (string): Price tier applied to this customer: `Standard`, `Silver`, `Gold`, `Platinum` or `Custom`. - paymentTerms (string): Default payment terms: `DueOnReceipt`, `Net15`, `Net30`, `Net45`, `Net60`, `COD` or `Prepaid`. - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. - currentBalance (number (double)): Outstanding balance owed by the customer, e.g. `1250.50`. - storeCreditBalance (number (double)): Store credit available to the customer, e.g. `0.00`. - status (string): Status flag: `None`, `Preferred` or `Difficult`. - taxId (string, nullable): Tax identification number, if recorded. - warningNotes (string, nullable): Warning shown to staff when the customer is selected (e.g. `"Pays late"`). - notes (string, nullable): Free-form internal notes. - isActive (boolean): False when the customer has been deactivated (kept for history, hidden from pickers). - createdAt (string (date-time)): UTC creation timestamp, e.g. `2026-03-14T09:12:00Z`. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited after creation. - 400: Malformed body, unknown enum value, or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 409: The key's creator no longer exists on this server, so the write cannot be attributed. — ProblemDetails - 422: Well-formed but invalid field values (problem lists the fields), or `Idempotency-Key` reused with a different body. — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails - 500: The customer could not be saved. — ProblemDetails ### GET /api/v1/customers/{id} Returns one customer. Send the previous `ETag` as `If-None-Match` to receive `304 Not Modified` when unchanged. Parameters: - id (path, integer (int32), required): Customer id from a list response. Responses: - 200: The customer, with an `ETag` header. — CustomerDto - id (integer (int32)): Numeric id used in URLs (e.g. `/api/v1/customers/42`). Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor and when correlating with offline devices. - name (string): Display name, e.g. `"Jane Doe"`. Always present. - companyName (string, nullable): Company / trading name when the customer is a business, e.g. `"Doe Roofing LLC"`. - customerType (string): Customer type: `EndCustomer`, `Installer`, `Distributor` or `Commercial`. - primaryContact (string, nullable): Name of the primary contact person, if different from `name`. - email (string, nullable): Contact e-mail address. - phone (string, nullable): Main phone number as entered (no normalisation). - mobilePhone (string, nullable): Mobile phone number as entered. - address (string, nullable): Primary (billing) street address line 1. - address2 (string, nullable): Primary address line 2 (suite, unit). - city (string, nullable): Primary address city. - state (string, nullable): Primary address state / province. - postalCode (string, nullable): Primary address ZIP / postal code. - country (string, nullable): Primary address country. - isTaxExempt (boolean, nullable): True when the customer is tax exempt; null when not recorded. - priceTier (string): Price tier applied to this customer: `Standard`, `Silver`, `Gold`, `Platinum` or `Custom`. - paymentTerms (string): Default payment terms: `DueOnReceipt`, `Net15`, `Net30`, `Net45`, `Net60`, `COD` or `Prepaid`. - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. - currentBalance (number (double)): Outstanding balance owed by the customer, e.g. `1250.50`. - storeCreditBalance (number (double)): Store credit available to the customer, e.g. `0.00`. - status (string): Status flag: `None`, `Preferred` or `Difficult`. - taxId (string, nullable): Tax identification number, if recorded. - warningNotes (string, nullable): Warning shown to staff when the customer is selected (e.g. `"Pays late"`). - notes (string, nullable): Free-form internal notes. - isActive (boolean): False when the customer has been deactivated (kept for history, hidden from pickers). - createdAt (string (date-time)): UTC creation timestamp, e.g. `2026-03-14T09:12:00Z`. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited after creation. - 304: Unchanged since the supplied `If-None-Match`. — no body - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such customer in this organization (also for deleted customers). — ProblemDetails - 429: Too Many Requests — ProblemDetails ### PUT /api/v1/customers/{id} Replaces the editable fields of one customer. Requires `ManageCustomers` and an `Idempotency-Key` header. This is a full update: every editable field takes the value in the body (omitted optional fields become null / defaults), so GET the customer first and send it back modified. Optional optimistic concurrency: send the `ETag` from that GET as `If-Match` to get `412` instead of overwriting a change made in the meantime. Balances, deletion state and audit fields cannot be changed here. Parameters: - id (path, integer (int32), required): Customer id. Request body (application/json): CustomerWriteRequest: The complete set of editable fields. - name (string, required): Display name, e.g. `"Jane Doe"`. Required, ≤ 200 characters. - companyName (string, nullable): Company / trading name, e.g. `"Doe Roofing LLC"`. ≤ 200. - customerType (CustomerType) - primaryContact (string, nullable): Primary contact person, if different from `name`. ≤ 100. - email (string (email), nullable): Contact e-mail address. ≤ 256. - phone (string (tel), nullable): Main phone number, stored as entered. ≤ 20. - mobilePhone (string (tel), nullable): Mobile phone number, stored as entered. ≤ 20. - address (string, nullable): Primary (billing) street address line 1. ≤ 200. On create, any of address/city/state/postalCode also creates a "Primary Address" location. - address2 (string, nullable): Primary address line 2 (suite, unit). ≤ 200. - city (string, nullable): Primary address city. ≤ 100. - state (string, nullable): Primary address state / province. ≤ 50. - postalCode (string, nullable): Primary address ZIP / postal code. ≤ 20. - country (string, nullable): Primary address country. ≤ 100. Defaults to `"USA"` on create. - taxId (string, nullable): Tax identification number. ≤ 50. - isTaxExempt (boolean, nullable): True when tax exempt; null when not recorded. - priceTier (PriceTier) - paymentTerms (PaymentTerms) - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. Default 0. - isActive (boolean): Update only — false deactivates the customer (kept for history, hidden from pickers). Ignored on create; new customers are always active. - status (CustomerStatus) - warningNotes (string, nullable): Warning shown to staff when the customer is selected. ≤ 1000. - notes (string, nullable): Free-form internal notes. Responses: - 200: The updated customer. — CustomerDto - id (integer (int32)): Numeric id used in URLs (e.g. `/api/v1/customers/42`). Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor and when correlating with offline devices. - name (string): Display name, e.g. `"Jane Doe"`. Always present. - companyName (string, nullable): Company / trading name when the customer is a business, e.g. `"Doe Roofing LLC"`. - customerType (string): Customer type: `EndCustomer`, `Installer`, `Distributor` or `Commercial`. - primaryContact (string, nullable): Name of the primary contact person, if different from `name`. - email (string, nullable): Contact e-mail address. - phone (string, nullable): Main phone number as entered (no normalisation). - mobilePhone (string, nullable): Mobile phone number as entered. - address (string, nullable): Primary (billing) street address line 1. - address2 (string, nullable): Primary address line 2 (suite, unit). - city (string, nullable): Primary address city. - state (string, nullable): Primary address state / province. - postalCode (string, nullable): Primary address ZIP / postal code. - country (string, nullable): Primary address country. - isTaxExempt (boolean, nullable): True when the customer is tax exempt; null when not recorded. - priceTier (string): Price tier applied to this customer: `Standard`, `Silver`, `Gold`, `Platinum` or `Custom`. - paymentTerms (string): Default payment terms: `DueOnReceipt`, `Net15`, `Net30`, `Net45`, `Net60`, `COD` or `Prepaid`. - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. - currentBalance (number (double)): Outstanding balance owed by the customer, e.g. `1250.50`. - storeCreditBalance (number (double)): Store credit available to the customer, e.g. `0.00`. - status (string): Status flag: `None`, `Preferred` or `Difficult`. - taxId (string, nullable): Tax identification number, if recorded. - warningNotes (string, nullable): Warning shown to staff when the customer is selected (e.g. `"Pays late"`). - notes (string, nullable): Free-form internal notes. - isActive (boolean): False when the customer has been deactivated (kept for history, hidden from pickers). - createdAt (string (date-time)): UTC creation timestamp, e.g. `2026-03-14T09:12:00Z`. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited after creation. - 400: Malformed body, unknown enum value, or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such customer in this organization (also for deleted customers). — ProblemDetails - 409: Concurrent edit detected, or the key's creator no longer exists on this server. — ProblemDetails - 412: `If-Match` does not match the customer's current `ETag`. — ProblemDetails - 422: Well-formed but invalid field values (problem lists the fields), or `Idempotency-Key` reused with a different body. — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails - 500: The customer could not be saved. — ProblemDetails ### GET /api/v1/customers/{id}/locations Lists the additional locations (job sites / addresses) of one customer. The customer's primary address is on the customer resource itself; this collection holds the extra sites jobs can be booked at. Cursor-paginated like every list. Parameters: - id (path, integer (int32), required): Customer id. - after (query, string, optional): SyncId of the last item of the previous page; omit for the first page. - limit (query, integer (int32), optional): Page size, 1–200 (default 50). - isActive (query, boolean, optional): Only active (`true`) or retired (`false`) locations; omit for both. Responses: - 200: A page of locations (may be empty). — PagedResponseOfCustomerLocationDto - items (CustomerLocationDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 400: Malformed `after` cursor. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such customer in this organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails ## Invoices ### GET /api/v1/invoices Lists invoice headers. Requires `ViewInvoices`. Cursor-paginated: pass the last invoice's `syncId` as `after`. Parameters: - status (query, InvoiceStatus, optional): Only invoices in this status (name such as `Sent` or numeric value). - jobId (query, integer (int32), optional): Only invoices billing this job. - customerId (query, integer (int32), optional): Only invoices billed to this customer. - updatedSince (query, string (date-time), optional): Only invoices created or modified at/after this UTC instant. - after (query, string, optional): SyncId of the last invoice of the previous page. - limit (query, integer (int32), optional): Page size, 1..200 (default 50). Responses: - 200: One page of invoice headers. — PagedResponseOfInvoiceSummary - items (InvoiceSummary[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 400: Unknown `after` cursor or unrecognized `status`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/invoices/from-job Creates a draft invoice for a job. Requires `ManageInvoices` and an `Idempotency-Key` header. Bills the job's full price — or, once a finalized invoice exists, only its unbilled remainder (a supplemental invoice). The job's down payment transfers onto the invoice; store credit is applied when requested. Every omitted field defaults exactly as the Generate Invoice page does. The result is a `Draft`: finalize it with `POST /api/v1/invoices/{id}/mark-sent`. Request body (application/json): CreateInvoiceFromJobRequest: What to bill and, optionally, how. - jobId (integer (int32)): Numeric id of the job to bill. - invoiceNumber (string, nullable): Invoice number. Defaults to `INV-{jobNumber}`, or `INV-{jobNumber}-{n}` for the n-th invoice on the job. - invoiceDate (string (date-time), nullable): Issue date (UTC, date part only). Defaults to today. - dueDate (string (date-time), nullable): Due date (UTC, date part only). Defaults from the customer's payment terms; must not precede `invoiceDate`. - taxRate (number (double), nullable): Tax rate percentage. Defaults to the job's rate; always 0 for a tax-exempt customer. - terms (string, nullable): Payment terms text printed on the invoice. Defaults to the customer's terms (e.g. "Net 30"). - notes (string, nullable): Notes shown to the customer. - paymentInstructions (string, nullable): Payment instructions printed on the invoice. Defaults to the organization's default instructions. - applyStoreCredit (boolean): Apply `storeCreditToApply` of the customer's store credit to the new invoice. - storeCreditToApply (number (double)): Store credit amount to apply when `applyStoreCredit` is true (capped at the customer's balance and the invoice balance). Ignored otherwise. Responses: - 201: The new draft invoice; `Location` points at it. — InvoiceSummary - id (integer (int32)): Numeric id, usable in `/api/v1/invoices/{id}`. - syncId (string (uuid)): Stable cross-device id; also the pagination cursor. - invoiceNumber (string): Human-readable invoice number, e.g. `INV-1042`. - status (InvoiceStatus) - customerId (integer (int32), nullable): Billed customer id, if any. - customerName (string, nullable): Billed customer name, if any. - jobId (integer (int32), nullable): Job the invoice bills, if any. - jobNumber (string, nullable): Job number, if any. - salesOrderId (integer (int32), nullable): Sales order the invoice bills, if any. - description (string, nullable): Optional description. - invoiceDate (string (date-time)): Issue date, ISO-8601 UTC at midnight, e.g. `2026-09-05T00:00:00Z`. - dueDate (string (date-time)): Due date, ISO-8601 UTC at midnight, e.g. `2026-10-05T00:00:00Z`. - subtotal (number (double)): Sum of lines before tax. - taxAmount (number (double)): Tax charged. - total (number (double)): Subtotal plus tax. - amountPaid (number (double)): Payments applied. - storeCreditApplied (number (double)): Store credit applied. - balanceDue (number (double)): Total minus payments and store credit. - isConsolidated (boolean): True when this invoice consolidates other invoices. - consolidatedIntoInvoiceId (integer (int32), nullable): Id of the consolidated invoice that absorbed this one, if any. - sentDate (string (date-time), nullable): When the invoice was first sent, ISO-8601 UTC (e.g. `2026-09-05T14:30:00Z`); null if never sent. - paidDate (string (date-time), nullable): When the invoice was paid in full, ISO-8601 UTC; null until paid. - createdAt (string (date-time)): Creation time, ISO-8601 UTC. - updatedAt (string (date-time), nullable): Last modification time, ISO-8601 UTC; null when never edited. Use with the `updatedSince` filter to poll for changes. - 400: Malformed body or out-of-range value. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such job in the calling organization. — ProblemDetails - 409: The job cannot be invoiced (cancelled, estimate option not chosen), a draft is already pending, the invoice number is taken, or the key's creator is unknown on this server. — ProblemDetails - 422: Nothing left to bill, or `dueDate` precedes `invoiceDate`. — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails - 500: The invoice could not be saved. — ProblemDetails ### GET /api/v1/invoices/{id} Returns one invoice with its lines and payments. Requires `ViewInvoices`. `id` may be the numeric id or the `syncId`. Sends a weak `ETag`; repeat with `If-None-Match` to get 304 when unchanged. Parameters: - id (path, string, required): Numeric id or SyncId. Responses: - 200: The invoice. — InvoiceDetail - invoice (InvoiceSummary) - lines (InvoiceLineDto[]): Lines in display order. - payments (PaymentsSummary) - terms (string, nullable): Payment terms text. - notes (string, nullable): Invoice notes shown to the customer. - paymentInstructions (string, nullable): Instructions printed on the invoice. - paymentLinkUrl (string, nullable): Online payment link, if one was generated. - taxRate (number (double)): Tax rate applied (percentage). - 304: Unchanged since the supplied ETag. — no body - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such invoice in the calling organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/invoices/{id}/mark-sent Finalizes a draft invoice (marks it sent). Requires `ManageInvoices` and an `Idempotency-Key` header. Freezes the draft's lines, records the send and moves it to `Sent` — the same step as the "Finalize" button, minus the customer email (send that through your own channel using `GET /api/v1/invoices/{id}`). Calling it on an already-finalized invoice only refreshes its audit fields and returns it unchanged. Parameters: - id (path, integer (int32), required): Numeric invoice id. Responses: - 200: The invoice after finalization. — InvoiceSummary - id (integer (int32)): Numeric id, usable in `/api/v1/invoices/{id}`. - syncId (string (uuid)): Stable cross-device id; also the pagination cursor. - invoiceNumber (string): Human-readable invoice number, e.g. `INV-1042`. - status (InvoiceStatus) - customerId (integer (int32), nullable): Billed customer id, if any. - customerName (string, nullable): Billed customer name, if any. - jobId (integer (int32), nullable): Job the invoice bills, if any. - jobNumber (string, nullable): Job number, if any. - salesOrderId (integer (int32), nullable): Sales order the invoice bills, if any. - description (string, nullable): Optional description. - invoiceDate (string (date-time)): Issue date, ISO-8601 UTC at midnight, e.g. `2026-09-05T00:00:00Z`. - dueDate (string (date-time)): Due date, ISO-8601 UTC at midnight, e.g. `2026-10-05T00:00:00Z`. - subtotal (number (double)): Sum of lines before tax. - taxAmount (number (double)): Tax charged. - total (number (double)): Subtotal plus tax. - amountPaid (number (double)): Payments applied. - storeCreditApplied (number (double)): Store credit applied. - balanceDue (number (double)): Total minus payments and store credit. - isConsolidated (boolean): True when this invoice consolidates other invoices. - consolidatedIntoInvoiceId (integer (int32), nullable): Id of the consolidated invoice that absorbed this one, if any. - sentDate (string (date-time), nullable): When the invoice was first sent, ISO-8601 UTC (e.g. `2026-09-05T14:30:00Z`); null if never sent. - paidDate (string (date-time), nullable): When the invoice was paid in full, ISO-8601 UTC; null until paid. - createdAt (string (date-time)): Creation time, ISO-8601 UTC. - updatedAt (string (date-time), nullable): Last modification time, ISO-8601 UTC; null when never edited. Use with the `updatedSince` filter to poll for changes. - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such invoice in the calling organization. — ProblemDetails - 409: The invoice is voided or was consolidated into another invoice, or the key's creator is unknown on this server. — ProblemDetails - 429: Too Many Requests — ProblemDetails ## Items ### GET /api/v1/items Lists items (templates) with pricing and total on-hand. Requires `ViewInventory`. Draft and deleted templates are excluded. Cursor-paginated: pass the last item's `syncId` as `after`. Parameters: - search (query, string, optional): Case-insensitive match against name and description. - categoryId (query, integer (int32), optional): Only items in this category (primary or assigned). - updatedSince (query, string (date-time), optional): Only items created or updated at/after this UTC instant. - after (query, string, optional): SyncId of the last item of the previous page. - limit (query, integer (int32), optional): Page size, 1..200 (default 50). Responses: - 200: One page of items. — PagedResponseOfItemSummary - items (ItemSummary[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 400: Unknown `after` cursor. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 429: Too Many Requests — ProblemDetails ### GET /api/v1/items/{id} Returns one item with its properties, price tiers, barcodes and per-location stock. Requires `ViewInventory`. `id` may be the numeric id or the `syncId`. Sends a weak `ETag`; repeat with `If-None-Match` to get 304 when unchanged. Parameters: - id (path, string, required): Numeric id or SyncId. Responses: - 200: The item. — ItemDetail - item (ItemSummary) - properties (object): Every template property by name (includes `Unit Price` and `Cost` when set). - priceTiers (ItemPriceTier[]): Quantity-break pricing, ascending by quantity. Empty when the item has a single price. - barcodes (ItemBarcodeDto[]): Barcodes attached to the item. - stock (StockSummary) - parentItemId (integer (int32), nullable): Id of the parent template for variant children; null otherwise. - 304: Unchanged since the supplied ETag. — no body - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such item in the calling organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails ## Jobs ### GET /api/v1/jobs Lists the organization's jobs, oldest first, one page at a time. Pagination: pass the previous response's `nextCursor` as `after`; `limit` defaults to 50 and is capped at 200. All filters combine with AND. Response carries a weak `ETag`; resend it as `If-None-Match` to get 304 when nothing changed. Parameters: - status (query, string, optional): Workflow status name to match exactly (case-insensitive), e.g. `Scheduled`, `InProgress`, `Completed`. - customerId (query, integer (int32), optional): Only jobs for this customer id. - updatedSince (query, string (date-time), optional): Only jobs created or modified at or after this instant (ISO-8601 UTC, e.g. `2026-09-01T00:00:00Z`). Use it to poll for changes. - scheduledFrom (query, string (date-time), optional): Only jobs whose scheduled start is at or after this local wall-clock time (`yyyy-MM-ddTHH:mm:ss`, no zone suffix). - scheduledTo (query, string (date-time), optional): Only jobs whose scheduled start is at or before this local wall-clock time. - after (query, string, optional): Cursor from the previous page's `nextCursor` (a job SyncId). Omit for the first page. - limit (query, integer (int32), optional): Page size, 1–200 (default 50). Responses: - 200: A page of jobs and the cursor for the next page (null on the last page). — PagedResponseOfJobSummaryDto - items (JobSummaryDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 304: Unchanged since the `If-None-Match` ETag. — no body - 400: Unknown `status` or unusable `after` cursor (field listed in `errors`). — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/jobs Creates a job (estimate or draft) for a customer, optionally with material, labor and custom-charge lines. Same code path as the MVC "Create job" page. Requires an `Idempotency-Key` header. Line totals are computed on the server from the lines you send. The job number is generated when omitted. Initial status: `Estimated`, or `Draft` when `isDraft` is true, unless `statusId` / `statusKey` names one of the organization's statuses. The write is attributed to the user who created the key. Request body (application/json): CreateJobApiRequest - customerId (integer (int32), required): Customer the job is for (must belong to the key's organization). - customerAddressId (integer (int32), nullable): Optional site address id; must belong to `customerId`. - jobNumber (string, nullable): Optional job number. Omit to have one generated; a duplicate is suffixed to stay unique. - jobType (string): Free-text job type, e.g. `Installation` (default), `Repair`, `Service`. - description (string, nullable): Short description of the work, shown in lists. ≤ 500 characters. Example: `Replace water heater`. - notes (string, nullable): Free-form internal notes (not shown to the customer). No length limit. - isDraft (boolean): Save as a draft (status `Draft`) instead of an estimate. Ignored when `statusId` / `statusKey` is given. - statusId (integer (int32), nullable): Initial status definition id (one of the org's statuses). Mutually exclusive with `statusKey`. - statusKey (string, nullable): Initial status by name — a status definition name (case-insensitive) or a system status key such as `Draft`, `Estimated`, `Scheduled`. Mutually exclusive with `statusId`. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall clock, `yyyy-MM-ddTHH:mm:ss` with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone); a trailing `Z` is converted to local time. Example: `2026-09-08T08:30:00`. - estimateChecklistTemplateId (integer (int32), nullable): Optional id of one of the organization's estimate pre-inspection checklist templates; its sections and items are copied onto the job. - items (CreateJobItemApiRequest[]): Material lines (item templates with quantity and pricing). May be empty. - laborItems (CreateJobLaborApiRequest[]): Labor lines (hours × rates). May be empty. - customCharges (CreateJobCustomChargeApiRequest[]): Custom charges (fees, disposal, permits …) with a price and optional cost. May be empty. Responses: - 201: The created job (same shape as `GET /api/v1/jobs/{id}`); `Location` points at it. — JobDetailDto - id (integer (int32)): Numeric id used in `/api/v1/jobs/{id}`. Unique within this organization. Example: `1042`. - syncId (string (uuid)): Stable cross-device identifier (GUID) — also the pagination cursor value. Example: `3f2504e0-4f89-11d3-9a0c-0305e82c3301`. - jobNumber (string): Human-readable job number shown to staff and customers. Example: `J-2026-0042`. - jobType (string, nullable): Free-text job category configured by the organization. Example: `Installation`, `Repair`. - status (string): Workflow status name. One of `Draft`, `InspectionInProgress`, `Estimated`, `AwaitingOptionSelection`, `Scheduled`, `InProgress`, `Completed`, `Invoiced`, `Paid`, `Cancelled`, `Outstanding`. - statusName (string): Display label of the status as the organization has customised it (falls back to `status`). Example: `On Site`. - paymentStatus (string, nullable): Payment state, independent of workflow status: `Unpaid`, `Partial`, `Paid`, `Overpaid` or `Refunded`. - customerId (integer (int32)): Id of the customer the job is for (see `/api/v1/customers/{id}`). - customerName (string): Customer display name at read time. Example: `Acme Property Management`. - description (string, nullable): Short description of the work. May be null for drafts. - createdAt (string (date-time)): When the job was created, ISO-8601 UTC. Example: `2026-09-01T14:03:22Z`. - updatedAt (string (date-time)): Last modification time, ISO-8601 UTC (creation time when never modified). Use with the `updatedSince` filter to poll for changes. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall-clock time with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone), e.g. `2026-09-08T08:30:00`. Null when unscheduled. - scheduledDurationMinutes (integer (int32), nullable): Planned duration in minutes (default 120 when scheduled without an explicit duration). Null when unscheduled. - startDate (string (date-time), nullable): When work actually started, ISO-8601 UTC. Null until the job is in progress. - completedDate (string (date-time), nullable): When work was completed, ISO-8601 UTC. Null until completed. - totalPrice (number (double)): Customer price before tax, after the job-level discount, in the organization's currency (decimal, e.g. `1250.00`). - notes (string, nullable): Internal notes typed on the job itself (not the timeline). Null when empty. - siteAddress (JobSiteAddressDto) - materialPrice (number (double)): Sum of material lines before job-level discount and tax (decimal currency, e.g. `800.00`). - laborPrice (number (double)): Sum of labor lines before job-level discount and tax (decimal currency). - customChargesTotal (number (double)): Sum of custom charges before job-level discount and tax (decimal currency). - discountPercent (number (double), nullable): Job-level discount as a percentage of every line (0–100), or null when a fixed amount / no discount applies. - discountAmount (number (double), nullable): Job-level fixed discount amount (decimal currency), or null when a percentage / no discount applies. - taxRate (number (double)): Tax rate locked in at creation, as a percentage (e.g. `8.25`). - downPaymentAmount (number (double), nullable): Money collected before invoicing (decimal currency), or null when none. - assignees (JobAssigneeDto[]): Staff assigned to the job (requires the Scheduling module to change). Empty when unassigned. - items (JobMaterialLineDto[]): Material lines (parts/products) on the job, excluding removed lines. - laborItems (JobLaborLineDto[]): Labor lines on the job, excluding removed lines. - updates (JobNoteDto[]): Timeline of notes, progress updates and status changes, oldest first. - 400: Malformed body, missing `Idempotency-Key`, or both `statusId` and `statusKey` given. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 409: The key's creator is unknown on this server, or the service refused the create. — ProblemDetails - 422: Unknown customer, site address, status, item template, labor rate or task template for this organization (field listed in `errors`). — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails ### GET /api/v1/jobs/{id} Returns one job with its site address, pricing, assignees, material and labor lines and note timeline. Removed (soft-deleted) lines and assignments are excluded. Response carries a weak `ETag`; resend it as `If-None-Match` to get 304 when nothing changed. Parameters: - id (path, integer (int32), required): Job id from the list endpoint. Responses: - 200: The job. — JobDetailDto - id (integer (int32)): Numeric id used in `/api/v1/jobs/{id}`. Unique within this organization. Example: `1042`. - syncId (string (uuid)): Stable cross-device identifier (GUID) — also the pagination cursor value. Example: `3f2504e0-4f89-11d3-9a0c-0305e82c3301`. - jobNumber (string): Human-readable job number shown to staff and customers. Example: `J-2026-0042`. - jobType (string, nullable): Free-text job category configured by the organization. Example: `Installation`, `Repair`. - status (string): Workflow status name. One of `Draft`, `InspectionInProgress`, `Estimated`, `AwaitingOptionSelection`, `Scheduled`, `InProgress`, `Completed`, `Invoiced`, `Paid`, `Cancelled`, `Outstanding`. - statusName (string): Display label of the status as the organization has customised it (falls back to `status`). Example: `On Site`. - paymentStatus (string, nullable): Payment state, independent of workflow status: `Unpaid`, `Partial`, `Paid`, `Overpaid` or `Refunded`. - customerId (integer (int32)): Id of the customer the job is for (see `/api/v1/customers/{id}`). - customerName (string): Customer display name at read time. Example: `Acme Property Management`. - description (string, nullable): Short description of the work. May be null for drafts. - createdAt (string (date-time)): When the job was created, ISO-8601 UTC. Example: `2026-09-01T14:03:22Z`. - updatedAt (string (date-time)): Last modification time, ISO-8601 UTC (creation time when never modified). Use with the `updatedSince` filter to poll for changes. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall-clock time with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone), e.g. `2026-09-08T08:30:00`. Null when unscheduled. - scheduledDurationMinutes (integer (int32), nullable): Planned duration in minutes (default 120 when scheduled without an explicit duration). Null when unscheduled. - startDate (string (date-time), nullable): When work actually started, ISO-8601 UTC. Null until the job is in progress. - completedDate (string (date-time), nullable): When work was completed, ISO-8601 UTC. Null until completed. - totalPrice (number (double)): Customer price before tax, after the job-level discount, in the organization's currency (decimal, e.g. `1250.00`). - notes (string, nullable): Internal notes typed on the job itself (not the timeline). Null when empty. - siteAddress (JobSiteAddressDto) - materialPrice (number (double)): Sum of material lines before job-level discount and tax (decimal currency, e.g. `800.00`). - laborPrice (number (double)): Sum of labor lines before job-level discount and tax (decimal currency). - customChargesTotal (number (double)): Sum of custom charges before job-level discount and tax (decimal currency). - discountPercent (number (double), nullable): Job-level discount as a percentage of every line (0–100), or null when a fixed amount / no discount applies. - discountAmount (number (double), nullable): Job-level fixed discount amount (decimal currency), or null when a percentage / no discount applies. - taxRate (number (double)): Tax rate locked in at creation, as a percentage (e.g. `8.25`). - downPaymentAmount (number (double), nullable): Money collected before invoicing (decimal currency), or null when none. - assignees (JobAssigneeDto[]): Staff assigned to the job (requires the Scheduling module to change). Empty when unassigned. - items (JobMaterialLineDto[]): Material lines (parts/products) on the job, excluding removed lines. - laborItems (JobLaborLineDto[]): Labor lines on the job, excluding removed lines. - updates (JobNoteDto[]): Timeline of notes, progress updates and status changes, oldest first. - 304: Unchanged since the `If-None-Match` ETag. — no body - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such job in this organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/jobs/{id}/assignees Assigns people to a job — additively by default, or as the complete new crew with `replace: true`. Requires the Scheduling module. User ids come from `GET /api/v1/users` and must be active members of the organization. People already on the job keep their crew (schedule group) context; newly added people are assigned as individuals. With `replace: true` anyone not listed is unassigned (an empty list clears the job). Requires an `Idempotency-Key` header. Parameters: - id (path, integer (int32), required): Job id from a list response. Request body (application/json): AssignJobRequest: User ids to assign and whether they replace the current set. - userIds (string[], required): Identity user ids (see `GET /api/v1/users`) to assign. All must be active members of the organization. - replace (boolean): `true`: the list becomes the complete set of assignees (people not listed are unassigned; an empty list unassigns everyone). `false` (default): the listed people are added to whoever is already assigned. Responses: - 200: Job id and a message listing who was assigned / unassigned (or "Assignments unchanged."). — ApiOperationResponse - id (integer (int32)): Id of the resource the operation acted on (e.g. the job id). - message (string, nullable): Human-readable confirmation from the service (e.g. "Job started."). May be null. - warning (string, nullable): Non-fatal problem encountered while completing the operation (e.g. a notification email that could not be sent). Null when there was none. - 400: `userIds` missing, or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Scheduling module not active for this organization. — ProblemDetails - 404: No such job in this organization. — ProblemDetails - 409: The key's creator is unknown on this server. — ProblemDetails - 422: One or more `userIds` are not active members of this organization (listed in the message). — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/jobs/{id}/cancel Cancels a job, returning its materials to stock. Same rules as the UI: a job that has received payment, or has sent/paid invoices, cannot be cancelled until the money is refunded / invoices voided (409). A job with an uncollected down payment can be cancelled with `issueStoreCredit: true` when the key holds `ManageStoreCredit`. Cancelling an already-cancelled job answers 200 with a `warning`. Requires an `Idempotency-Key` header. Parameters: - id (path, integer (int32), required): Job id from a list response. Request body (application/json): CancelJobRequest: Optional reason and the store-credit choice. - reason (string, nullable): Why the job is being cancelled (recorded on the job; "No reason provided" when omitted). - issueStoreCredit (boolean): When the job holds an uncollected down payment, convert it to customer store credit instead of blocking the cancellation. Requires the key to hold `ManageStoreCredit`. Responses: - 200: Job id, confirmation message and an optional warning. — ApiOperationResponse - id (integer (int32)): Id of the resource the operation acted on (e.g. the job id). - message (string, nullable): Human-readable confirmation from the service (e.g. "Job started."). May be null. - warning (string, nullable): Non-fatal problem encountered while completing the operation (e.g. a notification email that could not be sent). Null when there was none. - 400: Malformed body or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such job in this organization. — ProblemDetails - 409: Payments or invoices block the cancellation, or the key's creator is unknown on this server. — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/jobs/{id}/reschedule Moves a scheduled job to a new start time (and optionally a new end). Requires the Scheduling module. Only jobs in `Scheduled` status — or draft / estimated jobs that already carry a scheduled date — can be rescheduled; use `POST /jobs/{id}/status` to schedule an unscheduled job first. Times are the organization's local wall clock (`yyyy-MM-ddTHH:mm:ss`); a trailing `Z` is converted to local time. Requires an `Idempotency-Key` header. Parameters: - id (path, integer (int32), required): Job id from a list response. Request body (application/json): RescheduleJobRequest: New start, optional end and note. - start (string (date-time), required): New start as the organization's local wall clock, `yyyy-MM-ddTHH:mm:ss` with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone); a trailing `Z` is converted to local time. Example: `2026-09-08T08:30:00`. - end (string (date-time), nullable): Optional new end, same local wall-clock format as `start`; when given it must be after `start` and sets the job's duration. Omit to keep the current duration. - note (string, nullable): Optional reason recorded on the job. Responses: - 200: Job id and confirmation message. — ApiOperationResponse - id (integer (int32)): Id of the resource the operation acted on (e.g. the job id). - message (string, nullable): Human-readable confirmation from the service (e.g. "Job started."). May be null. - warning (string, nullable): Non-fatal problem encountered while completing the operation (e.g. a notification email that could not be sent). Null when there was none. - 400: `start` missing, `end` not after `start`, or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Scheduling module not active for this organization. — ProblemDetails - 404: No such job in this organization. — ProblemDetails - 409: The job's status does not allow rescheduling, or the key's creator is unknown on this server. — ProblemDetails - 429: Too Many Requests — ProblemDetails ### POST /api/v1/jobs/{id}/status Moves a job to another status. Name the target with `statusId` (one of the organization's status definitions) or `statusKey` (a status definition name, or a system key such as `InProgress` / `Completed`), not both. No transition rules are enforced — any status may follow any other, exactly as in the UI. Requires an `Idempotency-Key` header. Parameters: - id (path, integer (int32), required): Job id from a list response. Request body (application/json): SetJobStatusRequest: Target status and optional note. - statusId (integer (int32), nullable): Target status definition id (one of the org's statuses). - statusKey (string, nullable): Target status by name — a status definition name (case-insensitive) or a system status key such as `InProgress`, `Completed`. - note (string, nullable): Optional note appended to the status-change history entry. Responses: - 200: Job id and confirmation message. — ApiOperationResponse - id (integer (int32)): Id of the resource the operation acted on (e.g. the job id). - message (string, nullable): Human-readable confirmation from the service (e.g. "Job started."). May be null. - warning (string, nullable): Non-fatal problem encountered while completing the operation (e.g. a notification email that could not be sent). Null when there was none. - 400: Neither or both of `statusId` / `statusKey`, or missing `Idempotency-Key`. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 404: No such job in this organization. — ProblemDetails - 409: The key's creator is unknown on this server, or the service refused the change. — ProblemDetails - 422: Unknown status for this organization. — ValidationProblemDetails - 429: Too Many Requests — ProblemDetails ## Modules ### GET /api/v1/modules Lists the calling organization's modules and the presented key's permissions. Requires a valid API key but no specific permission. Module gating on other endpoints follows the `active` flag returned here. Responses: - 200: Modules with activation state, plus the key's permissions. — ModulesResponse - modules (ModuleSummary[]): Every catalog module with its activation state for this organization. - permissions (string[]): FlexPermissions names granted to the presented key. - 401: Unauthorized — ProblemDetails - 403: Forbidden — ProblemDetails - 429: Too Many Requests — ProblemDetails ## Schedule ### GET /api/v1/schedule Lists the jobs scheduled to start within a date range as calendar events. Times are the organization's local wall clock with no zone suffix (`2026-09-08T00:00:00`); a trailing `Z` is converted to local time first. The window may span at most 92 days. Events are not paginated — a quarter of jobs is returned in one array. Response carries a weak `ETag`; resend it as `If-None-Match` to get 304 when nothing changed. Parameters: - from (query, string (date-time), optional): Inclusive start of the window (required). Example: `2026-09-01T00:00:00`. - to (query, string (date-time), optional): Inclusive end of the window (required), at most 92 days after from. Example: `2026-09-30T23:59:59`. Responses: - 200: Calendar events ordered as the service returns them (by job). — ScheduleEventDto[] - id (integer (int32)): Job id — fetch details with `GET /api/v1/jobs/{id}`. - title (string): Event title: job number and customer, plus the description on a second line when present. Example: `J-2026-0042 - Acme Property`. - start (string): Start as the organization's local wall-clock time, `yyyy-MM-ddTHH:mm:ss` with no zone suffix. Example: `2026-09-08T08:30:00`. - end (string): End (start + duration), same format as `start`. - url (string, nullable): Relative API path of the job. Example: `/api/v1/jobs/1042`. - backgroundColor (string): Hex colour for the event body, derived from the job status. Example: `#28a745`. - borderColor (string): Hex colour for the event border (currently equal to `backgroundColor`). - displayTime (string): Start time formatted for display. Example: `8:30 AM`. - extendedProps (ScheduleEventDetailsDto) - 304: Unchanged since the `If-None-Match` ETag. — no body - 400: Missing parameter, `to` before `from`, or a window longer than 92 days (field listed in `errors`). — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: Key lacks `ViewJobs` or the Scheduling module is not active. — ProblemDetails - 429: Too Many Requests — ProblemDetails ## ScheduleGroups ### GET /api/v1/schedule-groups Lists schedule groups with their active members, one page at a time. Groups are flat with a `parentGroupId`; nest them client-side. Example: `GET /api/v1/schedule-groups?parentGroupId=3` returns the crews of division 3. Parameters: - after (query, string, optional): SyncId of the last item of the previous page; omit for the first page. - limit (query, integer (int32), optional): Page size, 1–200 (default 50). - updatedSince (query, string (date-time), optional): Only groups created or edited at/after this UTC instant (ISO-8601). - parentGroupId (query, integer (int32), optional): Only direct children of this group; pass `0` for top-level groups only. Responses: - 200: A page of groups. — PagedResponseOfScheduleGroupDto - items (ScheduleGroupDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 400: Malformed `after` cursor. — ValidationProblemDetails - 401: Unauthorized — ProblemDetails - 403: The `scheduling` module is not active for this organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails ## Users ### GET /api/v1/users Lists the organization's users with the schedule groups each belongs to. Users are keyed by string ids, so this collection is not cursor-paginated: the whole list is returned in one response (ordered by first then last name) with `nextCursor` always `null`. The response carries an `ETag`; send it as `If-None-Match` to get `304` when nothing changed. Example: `GET /api/v1/users?scheduleGroupId=5` returns the members of group 5. Parameters: - includeInactive (query, boolean, optional): Also return deactivated accounts (default `false`). - scheduleGroupId (query, integer (int32), optional): Only users who are members of this schedule group. Responses: - 200: All matching users. — PagedResponseOfUserSummaryDto - items (UserSummaryDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. - 304: Unchanged since the supplied `If-None-Match`. — no body - 401: Unauthorized — ProblemDetails - 403: The `scheduling` module is not active for this organization. — ProblemDetails - 404: scheduleGroupId is not a group of this organization. — ProblemDetails - 429: Too Many Requests — ProblemDetails # Schemas ## ApiOperationResponse Body of a successful workflow-style write that has no richer resource to return (assign, start, complete, void…). Id is the affected resource's id; Warning carries a non-fatal note from the service (e.g. an email that could not be sent). - id (integer (int32)): Id of the resource the operation acted on (e.g. the job id). - message (string, nullable): Human-readable confirmation from the service (e.g. "Job started."). May be null. - warning (string, nullable): Non-fatal problem encountered while completing the operation (e.g. a notification email that could not be sent). Null when there was none. ## AssignJobRequest Body of `POST /api/v1/jobs/{id}/assignees`. - userIds (string[], required): Identity user ids (see `GET /api/v1/users`) to assign. All must be active members of the organization. - replace (boolean): `true`: the list becomes the complete set of assignees (people not listed are unassigned; an empty list unassigns everyone). `false` (default): the listed people are added to whoever is already assigned. ## CancelJobRequest Body of `POST /api/v1/jobs/{id}/cancel`. - reason (string, nullable): Why the job is being cancelled (recorded on the job; "No reason provided" when omitted). - issueStoreCredit (boolean): When the job holds an uncollected down payment, convert it to customer store credit instead of blocking the cancellation. Requires the key to hold `ManageStoreCredit`. ## CreateInvoiceFromJobRequest Body of `POST /api/v1/invoices/from-job`. Only `jobId` is required; every omitted field defaults exactly as the Generate Invoice page does (number `INV-{jobNumber}` or `INV-{jobNumber}-{n}`, today, due date from the customer's payment terms, the job's tax rate, the organization's payment instructions). Amounts are never accepted: the subtotal and taxable portion come from the job — or, once a finalized invoice exists, from its unbilled remainder (a supplemental invoice). - jobId (integer (int32)): Numeric id of the job to bill. - invoiceNumber (string, nullable): Invoice number. Defaults to `INV-{jobNumber}`, or `INV-{jobNumber}-{n}` for the n-th invoice on the job. - invoiceDate (string (date-time), nullable): Issue date (UTC, date part only). Defaults to today. - dueDate (string (date-time), nullable): Due date (UTC, date part only). Defaults from the customer's payment terms; must not precede `invoiceDate`. - taxRate (number (double), nullable): Tax rate percentage. Defaults to the job's rate; always 0 for a tax-exempt customer. - terms (string, nullable): Payment terms text printed on the invoice. Defaults to the customer's terms (e.g. "Net 30"). - notes (string, nullable): Notes shown to the customer. - paymentInstructions (string, nullable): Payment instructions printed on the invoice. Defaults to the organization's default instructions. - applyStoreCredit (boolean): Apply `storeCreditToApply` of the customer's store credit to the new invoice. - storeCreditToApply (number (double)): Store credit amount to apply when `applyStoreCredit` is true (capped at the customer's balance and the invoice balance). Ignored otherwise. ## CreateJobApiRequest Body of `POST /api/v1/jobs` (API_AND_MCP.md, Phase 1b). Mirrors what the MVC Create page posts, minus the browser-only fields; totals are computed server-side from the lines instead of being trusted from the client. - customerId (integer (int32), required): Customer the job is for (must belong to the key's organization). - customerAddressId (integer (int32), nullable): Optional site address id; must belong to `customerId`. - jobNumber (string, nullable): Optional job number. Omit to have one generated; a duplicate is suffixed to stay unique. - jobType (string): Free-text job type, e.g. `Installation` (default), `Repair`, `Service`. - description (string, nullable): Short description of the work, shown in lists. ≤ 500 characters. Example: `Replace water heater`. - notes (string, nullable): Free-form internal notes (not shown to the customer). No length limit. - isDraft (boolean): Save as a draft (status `Draft`) instead of an estimate. Ignored when `statusId` / `statusKey` is given. - statusId (integer (int32), nullable): Initial status definition id (one of the org's statuses). Mutually exclusive with `statusKey`. - statusKey (string, nullable): Initial status by name — a status definition name (case-insensitive) or a system status key such as `Draft`, `Estimated`, `Scheduled`. Mutually exclusive with `statusId`. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall clock, `yyyy-MM-ddTHH:mm:ss` with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone); a trailing `Z` is converted to local time. Example: `2026-09-08T08:30:00`. - estimateChecklistTemplateId (integer (int32), nullable): Optional id of one of the organization's estimate pre-inspection checklist templates; its sections and items are copied onto the job. - items (CreateJobItemApiRequest[]): Material lines (item templates with quantity and pricing). May be empty. - laborItems (CreateJobLaborApiRequest[]): Labor lines (hours × rates). May be empty. - customCharges (CreateJobCustomChargeApiRequest[]): Custom charges (fees, disposal, permits …) with a price and optional cost. May be empty. ## CreateJobCustomChargeApiRequest A custom charge on a new job. - name (string, required): Charge name shown on the estimate, ≤ 200 characters. Required. Example: `Disposal fee`. - description (string, nullable): Optional longer description, ≤ 500 characters. - price (number (double)): Amount charged to the customer, in the organization's currency, ≥ 0. Example: `75.00`. - cost (number (double)): Cost to the organization (for margin), in its currency, ≥ 0. Default 0. ## CreateJobItemApiRequest A material line on a new job. - itemTemplateId (integer (int32)): Item template id from `GET /api/v1/items`; must belong to the key's organization. - quantity (integer (int32)): Whole units, ≥ 1. Default 1. - unitCost (number (double)): Cost per unit to the organization, in its currency, ≥ 0. Example: `12.50`. - unitPrice (number (double)): Price per unit charged to the customer, in the organization's currency, ≥ 0. Example: `19.99`. - discountPercent (number (double)): Line discount as a percentage, 0–100 (`10` = 10 % off `quantity` × `unitPrice`). Default 0. - notes (string, nullable): Line note, ≤ 500 characters. ## CreateJobLaborApiRequest A labor line on a new job. - description (string, required): What the labor is, ≤ 200 characters. Required. Example: `Install and commission unit`. - taskTemplateId (integer (int32), nullable): Optional id of the organization's task template this line derives from. - laborRateId (integer (int32), nullable): Optional id of the organization's labor rate the hourly figures were taken from (the figures themselves are still sent explicitly). - estimatedHours (number (double)): Estimated hours, decimal ≥ 0. Example: `2.5`. - customerRate (number (double)): Hourly rate billed to the customer. - employeeCost (number (double)): Hourly cost of the technician. - overheadFactor (number (double)): Multiplier applied to `employeeCost` for overhead (default 1). - notes (string, nullable): Line note, ≤ 500 characters. ## CustomerDto A customer of the calling organization as returned by `GET /api/v1/customers` and `GET /api/v1/customers/{id}`. Money values are decimals in the organization's currency; dates are ISO-8601 UTC. Additional job-site addresses are a separate collection at `/api/v1/customers/{id}/locations`. - id (integer (int32)): Numeric id used in URLs (e.g. `/api/v1/customers/42`). Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor and when correlating with offline devices. - name (string): Display name, e.g. `"Jane Doe"`. Always present. - companyName (string, nullable): Company / trading name when the customer is a business, e.g. `"Doe Roofing LLC"`. - customerType (string): Customer type: `EndCustomer`, `Installer`, `Distributor` or `Commercial`. - primaryContact (string, nullable): Name of the primary contact person, if different from `name`. - email (string, nullable): Contact e-mail address. - phone (string, nullable): Main phone number as entered (no normalisation). - mobilePhone (string, nullable): Mobile phone number as entered. - address (string, nullable): Primary (billing) street address line 1. - address2 (string, nullable): Primary address line 2 (suite, unit). - city (string, nullable): Primary address city. - state (string, nullable): Primary address state / province. - postalCode (string, nullable): Primary address ZIP / postal code. - country (string, nullable): Primary address country. - isTaxExempt (boolean, nullable): True when the customer is tax exempt; null when not recorded. - priceTier (string): Price tier applied to this customer: `Standard`, `Silver`, `Gold`, `Platinum` or `Custom`. - paymentTerms (string): Default payment terms: `DueOnReceipt`, `Net15`, `Net30`, `Net45`, `Net60`, `COD` or `Prepaid`. - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. - currentBalance (number (double)): Outstanding balance owed by the customer, e.g. `1250.50`. - storeCreditBalance (number (double)): Store credit available to the customer, e.g. `0.00`. - status (string): Status flag: `None`, `Preferred` or `Difficult`. - taxId (string, nullable): Tax identification number, if recorded. - warningNotes (string, nullable): Warning shown to staff when the customer is selected (e.g. `"Pays late"`). - notes (string, nullable): Free-form internal notes. - isActive (boolean): False when the customer has been deactivated (kept for history, hidden from pickers). - createdAt (string (date-time)): UTC creation timestamp, e.g. `2026-03-14T09:12:00Z`. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited after creation. ## CustomerLocationDto An additional address / job site belonging to a customer, as returned by `GET /api/v1/customers/{id}/locations`. Jobs can reference one of these as the site to work at; the customer's primary address is on the customer itself. - id (integer (int32)): Numeric id of the location. Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor. - customerId (integer (int32)): Id of the owning customer (matches the `{id}` in the URL). - name (string): Label given to the location, e.g. `"Main Office"` or `"Rental Property 1"`. - address (string, nullable): Street address line 1. - address2 (string, nullable): Street address line 2 (suite, unit). - city (string, nullable): City. - state (string, nullable): State / province. - postalCode (string, nullable): ZIP / postal code. - country (string, nullable): Country. - isMailingAddress (boolean): True when this is the customer's mailing address. - isActive (boolean): False when the location has been retired and should not be offered for new jobs. - notes (string, nullable): Free-text notes about the site (gate codes, parking), max 500 characters. - createdAt (string (date-time)): UTC creation timestamp. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited. ## CustomerStatus ## CustomerType ## CustomerWriteRequest Body of `POST /api/v1/customers` and `PUT /api/v1/customers/{id}`. PUT replaces every editable field, so send the current value of anything you want to keep; omitted optional fields become null. Enum fields accept their names (case-insensitive) or numeric values. Balances (`currentBalance`, `storeCreditBalance`) are maintained by invoicing and cannot be set here. - name (string, required): Display name, e.g. `"Jane Doe"`. Required, ≤ 200 characters. - companyName (string, nullable): Company / trading name, e.g. `"Doe Roofing LLC"`. ≤ 200. - customerType (CustomerType) - primaryContact (string, nullable): Primary contact person, if different from `name`. ≤ 100. - email (string (email), nullable): Contact e-mail address. ≤ 256. - phone (string (tel), nullable): Main phone number, stored as entered. ≤ 20. - mobilePhone (string (tel), nullable): Mobile phone number, stored as entered. ≤ 20. - address (string, nullable): Primary (billing) street address line 1. ≤ 200. On create, any of address/city/state/postalCode also creates a "Primary Address" location. - address2 (string, nullable): Primary address line 2 (suite, unit). ≤ 200. - city (string, nullable): Primary address city. ≤ 100. - state (string, nullable): Primary address state / province. ≤ 50. - postalCode (string, nullable): Primary address ZIP / postal code. ≤ 20. - country (string, nullable): Primary address country. ≤ 100. Defaults to `"USA"` on create. - taxId (string, nullable): Tax identification number. ≤ 50. - isTaxExempt (boolean, nullable): True when tax exempt; null when not recorded. - priceTier (PriceTier) - paymentTerms (PaymentTerms) - creditLimit (number (double)): Credit limit in the organization's currency, e.g. `5000.00`. Default 0. - isActive (boolean): Update only — false deactivates the customer (kept for history, hidden from pickers). Ignored on create; new customers are always active. - status (CustomerStatus) - warningNotes (string, nullable): Warning shown to staff when the customer is selected. ≤ 1000. - notes (string, nullable): Free-form internal notes. ## InvoiceDetail Full invoice as returned by `GET /api/v1/invoices/{id}`. - invoice (InvoiceSummary) - lines (InvoiceLineDto[]): Lines in display order. - payments (PaymentsSummary) - terms (string, nullable): Payment terms text. - notes (string, nullable): Invoice notes shown to the customer. - paymentInstructions (string, nullable): Instructions printed on the invoice. - paymentLinkUrl (string, nullable): Online payment link, if one was generated. - taxRate (number (double)): Tax rate applied (percentage). ## InvoiceLineDto One invoice line. - id (integer (int32)): Line id. - syncId (string (uuid)): Stable cross-device id. - description (string): Line description. - itemType (InvoiceLineItemType) - quantity (number (double)): Quantity billed. - unitOfMeasure (string, nullable): Unit of measure, if any. - unitPrice (number (double)): Price per unit after discount. - originalUnitPrice (number (double), nullable): Price per unit before discount, when a discount applied. - discountPercent (number (double)): Discount percentage applied to the line. - discountAmount (number (double)): Discount amount applied to the line. - total (number (double)): Quantity × unit price. - notes (string, nullable): Line notes, if any. - sortOrder (integer (int32)): Display order. ## InvoiceLineItemType ## InvoicePaymentDto A payment recorded against an invoice. - id (integer (int32)): Payment id. - syncId (string (uuid)): Stable cross-device id. - amount (number (double)): Amount paid. - paymentDate (string (date-time)): Payment date, ISO-8601 UTC, e.g. `2026-09-05T14:30:00Z`. - paymentMethod (string, nullable): Method (e.g. Cash, Card, Check), if recorded. - referenceNumber (string, nullable): Check/transaction reference, if recorded. ## InvoiceStatus ## InvoiceSummary Invoice header as listed by `GET /api/v1/invoices`. Timestamps are ISO-8601 UTC (`2026-09-05T14:30:00Z`). - id (integer (int32)): Numeric id, usable in `/api/v1/invoices/{id}`. - syncId (string (uuid)): Stable cross-device id; also the pagination cursor. - invoiceNumber (string): Human-readable invoice number, e.g. `INV-1042`. - status (InvoiceStatus) - customerId (integer (int32), nullable): Billed customer id, if any. - customerName (string, nullable): Billed customer name, if any. - jobId (integer (int32), nullable): Job the invoice bills, if any. - jobNumber (string, nullable): Job number, if any. - salesOrderId (integer (int32), nullable): Sales order the invoice bills, if any. - description (string, nullable): Optional description. - invoiceDate (string (date-time)): Issue date, ISO-8601 UTC at midnight, e.g. `2026-09-05T00:00:00Z`. - dueDate (string (date-time)): Due date, ISO-8601 UTC at midnight, e.g. `2026-10-05T00:00:00Z`. - subtotal (number (double)): Sum of lines before tax. - taxAmount (number (double)): Tax charged. - total (number (double)): Subtotal plus tax. - amountPaid (number (double)): Payments applied. - storeCreditApplied (number (double)): Store credit applied. - balanceDue (number (double)): Total minus payments and store credit. - isConsolidated (boolean): True when this invoice consolidates other invoices. - consolidatedIntoInvoiceId (integer (int32), nullable): Id of the consolidated invoice that absorbed this one, if any. - sentDate (string (date-time), nullable): When the invoice was first sent, ISO-8601 UTC (e.g. `2026-09-05T14:30:00Z`); null if never sent. - paidDate (string (date-time), nullable): When the invoice was paid in full, ISO-8601 UTC; null until paid. - createdAt (string (date-time)): Creation time, ISO-8601 UTC. - updatedAt (string (date-time), nullable): Last modification time, ISO-8601 UTC; null when never edited. Use with the `updatedSince` filter to poll for changes. ## ItemBarcodeDto A barcode attached to an item. - value (string): Encoded value. - type (string): Symbology (e.g. `UPC`, `Code128`). - isPrimary (boolean): True for the barcode printed on labels. ## ItemDetail Full item as returned by `GET /api/v1/items/{id}`. - item (ItemSummary) - properties (object): Every template property by name (includes `Unit Price` and `Cost` when set). - priceTiers (ItemPriceTier[]): Quantity-break pricing, ascending by quantity. Empty when the item has a single price. - barcodes (ItemBarcodeDto[]): Barcodes attached to the item. - stock (StockSummary) - parentItemId (integer (int32), nullable): Id of the parent template for variant children; null otherwise. ## ItemPriceTier Quantity-break price. - minQuantity (integer (int32)): Smallest quantity at which UnitPrice applies. - unitPrice (number (double)): Price per unit at or above MinQuantity. ## ItemSummary One sellable/stockable item (an `ItemTemplate`) as listed by `GET /api/v1/items`. Physical rows in inventory are instances of a template; the API exposes templates because that is what agents price, sell and reorder. - id (integer (int32)): Numeric id, usable in `/api/v1/items/{id}`. - syncId (string (uuid)): Stable cross-device id; also the pagination cursor. - name (string): Display name. - description (string, nullable): Free-text description, if any. - categoryId (integer (int32), nullable): Primary category id, if any. - categoryName (string, nullable): Primary category name, if any. - unitPrice (number (double), nullable): Selling price from the template's `Unit Price` property; null when not priced. - cost (number (double), nullable): Purchase cost from the template's `Cost` property; null when unknown. - onHand (number (double)): Units on hand across stock locations (job sites excluded). Decimal because items sold by sub-unit report partial rows; negative when the organization allows overselling. - isMiscItem (boolean): True for ad-hoc "misc" items that never carry stock. - isParentTemplate (boolean): True for a grouping template whose children are the stocked variants. - sellByUnits (boolean): True when the item is sold in sub-units of a parent row. - unitName (string, nullable): Name of the sub-unit when SellByUnits is true. - createdAt (string (date-time)): UTC creation time. - updatedAt (string (date-time), nullable): UTC last modification time; null when never edited. ## JobAssigneeDto One staff member assigned to a job. - userId (string): Identity user id of the assignee (matches `/api/v1/users`). - name (string): Display name (first + last, or email when no name is set). Example: `Maria Lopez`. - scheduleGroupId (integer (int32), nullable): Schedule group (crew) the assignment was made under, or null. - scheduleGroupName (string, nullable): Display name of `scheduleGroupId`. Example: `Crew A`. - notes (string, nullable): Note attached to the assignment, if any. ## JobDetailDto Full job as returned by `GET /api/v1/jobs/{id}`: everything in `JobSummaryDto` plus the job site, pricing breakdown, assignees, material and labor lines and the note/update timeline. - id (integer (int32)): Numeric id used in `/api/v1/jobs/{id}`. Unique within this organization. Example: `1042`. - syncId (string (uuid)): Stable cross-device identifier (GUID) — also the pagination cursor value. Example: `3f2504e0-4f89-11d3-9a0c-0305e82c3301`. - jobNumber (string): Human-readable job number shown to staff and customers. Example: `J-2026-0042`. - jobType (string, nullable): Free-text job category configured by the organization. Example: `Installation`, `Repair`. - status (string): Workflow status name. One of `Draft`, `InspectionInProgress`, `Estimated`, `AwaitingOptionSelection`, `Scheduled`, `InProgress`, `Completed`, `Invoiced`, `Paid`, `Cancelled`, `Outstanding`. - statusName (string): Display label of the status as the organization has customised it (falls back to `status`). Example: `On Site`. - paymentStatus (string, nullable): Payment state, independent of workflow status: `Unpaid`, `Partial`, `Paid`, `Overpaid` or `Refunded`. - customerId (integer (int32)): Id of the customer the job is for (see `/api/v1/customers/{id}`). - customerName (string): Customer display name at read time. Example: `Acme Property Management`. - description (string, nullable): Short description of the work. May be null for drafts. - createdAt (string (date-time)): When the job was created, ISO-8601 UTC. Example: `2026-09-01T14:03:22Z`. - updatedAt (string (date-time)): Last modification time, ISO-8601 UTC (creation time when never modified). Use with the `updatedSince` filter to poll for changes. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall-clock time with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone), e.g. `2026-09-08T08:30:00`. Null when unscheduled. - scheduledDurationMinutes (integer (int32), nullable): Planned duration in minutes (default 120 when scheduled without an explicit duration). Null when unscheduled. - startDate (string (date-time), nullable): When work actually started, ISO-8601 UTC. Null until the job is in progress. - completedDate (string (date-time), nullable): When work was completed, ISO-8601 UTC. Null until completed. - totalPrice (number (double)): Customer price before tax, after the job-level discount, in the organization's currency (decimal, e.g. `1250.00`). - notes (string, nullable): Internal notes typed on the job itself (not the timeline). Null when empty. - siteAddress (JobSiteAddressDto) - materialPrice (number (double)): Sum of material lines before job-level discount and tax (decimal currency, e.g. `800.00`). - laborPrice (number (double)): Sum of labor lines before job-level discount and tax (decimal currency). - customChargesTotal (number (double)): Sum of custom charges before job-level discount and tax (decimal currency). - discountPercent (number (double), nullable): Job-level discount as a percentage of every line (0–100), or null when a fixed amount / no discount applies. - discountAmount (number (double), nullable): Job-level fixed discount amount (decimal currency), or null when a percentage / no discount applies. - taxRate (number (double)): Tax rate locked in at creation, as a percentage (e.g. `8.25`). - downPaymentAmount (number (double), nullable): Money collected before invoicing (decimal currency), or null when none. - assignees (JobAssigneeDto[]): Staff assigned to the job (requires the Scheduling module to change). Empty when unassigned. - items (JobMaterialLineDto[]): Material lines (parts/products) on the job, excluding removed lines. - laborItems (JobLaborLineDto[]): Labor lines on the job, excluding removed lines. - updates (JobNoteDto[]): Timeline of notes, progress updates and status changes, oldest first. ## JobLaborLineDto A labor line on a job. - id (integer (int32)): Line id. - syncId (string (uuid)): Stable cross-device identifier of the line. - description (string): What the work is. Example: `Replace torsion springs`. - estimatedHours (number (double)): Hours quoted (decimal, e.g. `1.5`). - actualHours (number (double)): Hours actually worked (decimal). Zero until logged. - customerRate (number (double)): Hourly rate billed to the customer (decimal currency). - customerTotal (number (double)): Customer price for the line after line discount, before job discount and tax (decimal currency). - technicianId (string, nullable): Identity user id of the technician the line is booked to, or null. - status (string): Line state name from the LaborItemStatus enum (e.g. `Planned`, `Scheduled`, `Completed`). ## JobMaterialLineDto A material (part/product) line on a job. - id (integer (int32)): Line id. - syncId (string (uuid)): Stable cross-device identifier of the line. - itemTemplateId (integer (int32)): Item template (catalog product) id — see `/api/v1/items/{id}`. - itemTemplateName (string): Catalog product name at read time. Example: `Torsion Spring 2in x 32in`. - quantity (integer (int32)): Quantity ordered for the job (whole units). - deliveredQuantity (integer (int32)): Quantity already delivered to / used at the site. - unitPrice (number (double)): Customer unit price before line discount (decimal currency). - discountPercent (number (double)): Line discount as a percentage (0–100). - discountAmount (number (double)): Line discount as a fixed amount (decimal currency). - totalPrice (number (double)): Extended customer price after line discount, before job discount and tax (decimal currency). - status (string): Fulfilment state: `Planned`, `Reserved`, `Used` (delivered) or `Returned`. ## JobNoteDto One entry on a job's timeline (note, progress update, issue, status change…). - id (integer (int32)): Entry id. - syncId (string (uuid)): Stable cross-device identifier of the entry. - type (string): Kind of entry — JobUpdateType name such as `GeneralNote`, `ProgressUpdate`, `Issue`, `CustomerRequest` or `StatusChange`. - description (string): The note text. - isInternal (boolean): True when the note is staff-only and must not be shown to the customer. - createdAt (string (date-time)): When the entry was written, ISO-8601 UTC. - createdByName (string, nullable): Display name of the author, when recorded. - oldStatus (string, nullable): For status-change entries: the previous status name; otherwise null. - newStatus (string, nullable): For status-change entries: the new status name; otherwise null. ## JobSiteAddressDto A customer address used as the job site. - id (integer (int32)): Customer address id (see `/api/v1/customers/{id}/locations`). - name (string): Label the customer gave the address. Example: `Main Office`. - address (string, nullable): Street line 1. - address2 (string, nullable): Street line 2 (suite, unit). - city (string, nullable): City. - state (string, nullable): State / province. - postalCode (string, nullable): Postal / ZIP code. - country (string, nullable): Country, when recorded. ## JobSummaryDto One job as returned by `GET /api/v1/jobs`. A compact row for lists, boards and agents; call `GET /api/v1/jobs/{id}` for assignees, line items and notes. - id (integer (int32)): Numeric id used in `/api/v1/jobs/{id}`. Unique within this organization. Example: `1042`. - syncId (string (uuid)): Stable cross-device identifier (GUID) — also the pagination cursor value. Example: `3f2504e0-4f89-11d3-9a0c-0305e82c3301`. - jobNumber (string): Human-readable job number shown to staff and customers. Example: `J-2026-0042`. - jobType (string, nullable): Free-text job category configured by the organization. Example: `Installation`, `Repair`. - status (string): Workflow status name. One of `Draft`, `InspectionInProgress`, `Estimated`, `AwaitingOptionSelection`, `Scheduled`, `InProgress`, `Completed`, `Invoiced`, `Paid`, `Cancelled`, `Outstanding`. - statusName (string): Display label of the status as the organization has customised it (falls back to `status`). Example: `On Site`. - paymentStatus (string, nullable): Payment state, independent of workflow status: `Unpaid`, `Partial`, `Paid`, `Overpaid` or `Refunded`. - customerId (integer (int32)): Id of the customer the job is for (see `/api/v1/customers/{id}`). - customerName (string): Customer display name at read time. Example: `Acme Property Management`. - description (string, nullable): Short description of the work. May be null for drafts. - createdAt (string (date-time)): When the job was created, ISO-8601 UTC. Example: `2026-09-01T14:03:22Z`. - updatedAt (string (date-time)): Last modification time, ISO-8601 UTC (creation time when never modified). Use with the `updatedSince` filter to poll for changes. - scheduledDate (string (date-time), nullable): Scheduled start as the organization's local wall-clock time with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone), e.g. `2026-09-08T08:30:00`. Null when unscheduled. - scheduledDurationMinutes (integer (int32), nullable): Planned duration in minutes (default 120 when scheduled without an explicit duration). Null when unscheduled. - startDate (string (date-time), nullable): When work actually started, ISO-8601 UTC. Null until the job is in progress. - completedDate (string (date-time), nullable): When work was completed, ISO-8601 UTC. Null until completed. - totalPrice (number (double)): Customer price before tax, after the job-level discount, in the organization's currency (decimal, e.g. `1250.00`). ## LocationStock Stock held at one location. - locationId (integer (int32)): Location id. - locationName (string): Location name. - quantity (integer (int32)): Whole item rows at the location (not deleted, not in a job site). ## ModuleSummary One module from the catalog and whether the calling organization has it. - key (string): Stable module key (e.g. `scheduling`). Use this in code, never Name. - name (string): Display name. - active (boolean): True when the organization can use the module (included tier or activated). ## ModulesResponse Capability discovery for an API key: the org's modules and the key's permissions. - modules (ModuleSummary[]): Every catalog module with its activation state for this organization. - permissions (string[]): FlexPermissions names granted to the presented key. ## PagedResponseOfCustomerDto One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (CustomerDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfCustomerLocationDto One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (CustomerLocationDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfInvoiceSummary One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (InvoiceSummary[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfItemSummary One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (ItemSummary[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfJobSummaryDto One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (JobSummaryDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfScheduleGroupDto One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (ScheduleGroupDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PagedResponseOfUserSummaryDto One page of a cursor-paginated /api/v1 collection (API_AND_MCP.md: `?after=&limit=`). - items (UserSummaryDto[]): Items of this page in ascending creation order. - nextCursor (string, nullable): SyncId of the last item; pass it back as `?after=` to fetch the next page. `null` when this is the last page. ## PaymentTerms ## PaymentsSummary Payments applied to an invoice. - count (integer (int32)): Number of payments. - totalPaid (number (double)): Sum of payment amounts. - lastPaymentDate (string (date-time), nullable): Most recent payment date, ISO-8601 UTC; null when no payment has been recorded. - payments (InvoicePaymentDto[]): Individual payments, oldest first. ## PriceTier ## ProblemDetails RFC 7807 problem detail (application/problem+json). Also carries a traceId string to quote in support requests. - type (string, nullable): URI identifying the problem type (RFC 7807). Currently the generic reference for the HTTP status. - title (string, nullable): Short human-readable summary of the status, e.g. "Not Found". - status (integer (int32), nullable): HTTP status code, repeated from the response line. - detail (string, nullable): Explanation specific to this occurrence, e.g. "Job 42 was not found in this organization." - instance (string, nullable): Request path the problem occurred on, when known. ## RescheduleJobRequest Body of `POST /api/v1/jobs/{id}/reschedule`. - start (string (date-time), required): New start as the organization's local wall clock, `yyyy-MM-ddTHH:mm:ss` with no UTC offset (the `date-time` format here is a local time, not RFC 3339 with zone); a trailing `Z` is converted to local time. Example: `2026-09-08T08:30:00`. - end (string (date-time), nullable): Optional new end, same local wall-clock format as `start`; when given it must be after `start` and sets the job's duration. Omit to keep the current duration. - note (string, nullable): Optional reason recorded on the job. ## ScheduleEventAssigneeDto An assignee on a schedule event. - userId (string): Identity user id (matches `/api/v1/users`). - name (string): Display name. Example: `Maria Lopez`. - groupId (integer (int32), nullable): Crew the assignment falls under (explicit, or the user's primary group), or null. - groupName (string, nullable): Crew display name, or null. - groupColor (string, nullable): Crew hex colour, or null. - groupOrder (integer (int32)): Crew sort order (lower first; `2147483647` when the user has no crew). ## ScheduleEventDetailsDto Job facts attached to a `ScheduleEventDto`. - jobNumber (string): Human-readable job number. Example: `J-2026-0042`. - customerName (string): Customer display name. - jobType (string, nullable): Job category. Example: `Installation`. - status (string): Workflow status name (see `JobSummaryDto.Status` for the list). - totalPrice (number (double)): Customer price before tax (decimal currency). - description (string): Job description, empty string when none. - scheduledTime (string): Start time formatted for display. Example: `8:30 AM`. - durationMinutes (integer (int32)): Planned duration in minutes. Example: `120`. - durationText (string): Duration formatted for display. Example: `2h`. - assignees (ScheduleEventAssigneeDto[]): Assigned staff, ordered by crew then name. - assigneeNames (string): Comma-separated assignee names for one-line display. Empty when unassigned. - groupIds (integer (int32)[]): Distinct schedule group (crew) ids of the assignees. - groupColor (string, nullable): Hex colour of the first assignee's crew, used as the event accent; null when no crew colour applies. ## ScheduleEventDto One scheduled job as a calendar event (`GET /api/v1/schedule`). Same shape as the feed the web calendar consumes (`Jobs/GetCalendarEvents`) so a FullCalendar-style client can render it directly. - id (integer (int32)): Job id — fetch details with `GET /api/v1/jobs/{id}`. - title (string): Event title: job number and customer, plus the description on a second line when present. Example: `J-2026-0042 - Acme Property`. - start (string): Start as the organization's local wall-clock time, `yyyy-MM-ddTHH:mm:ss` with no zone suffix. Example: `2026-09-08T08:30:00`. - end (string): End (start + duration), same format as `start`. - url (string, nullable): Relative API path of the job. Example: `/api/v1/jobs/1042`. - backgroundColor (string): Hex colour for the event body, derived from the job status. Example: `#28a745`. - borderColor (string): Hex colour for the event border (currently equal to `backgroundColor`). - displayTime (string): Start time formatted for display. Example: `8:30 AM`. - extendedProps (ScheduleEventDetailsDto) ## ScheduleGroupDto A scheduling group (division, crew or team) of the calling organization, as returned by `GET /api/v1/schedule-groups`. Groups form a tree via `parentGroupId`; a user may belong to several groups. Requires the `scheduling` module. - id (integer (int32)): Numeric id of the group. Stable within this organization. - syncId (string (uuid)): Globally unique sync id (GUID). Use it as the `after` pagination cursor. - name (string): Group name, e.g. `"Install Crew A"`. - description (string, nullable): Optional description shown on the dispatch board. - colorHex (string, nullable): Hex colour used for calendar / board rendering, e.g. `"#2e7d32"`; null when unset. - parentGroupId (integer (int32), nullable): Id of the parent group (e.g. the division a crew belongs to); null for top-level groups. - parentGroupName (string, nullable): Name of the parent group; null for top-level groups. - displayOrder (integer (int32)): Sort position among siblings (ascending). Pages are returned in id order, so sort by this client-side when rendering a board. - members (ScheduleGroupMemberDto[]): Members of this group, leads first. Only active users are listed. - createdAt (string (date-time)): UTC creation timestamp. - updatedAt (string (date-time), nullable): UTC timestamp of the last edit; null when never edited. ## ScheduleGroupMemberDto A user's membership in a schedule group. - userId (string): User id (string GUID) — matches `id` from `GET /api/v1/users` and job assignee ids. - displayName (string): User's display name, e.g. `"Sam Carter"`. - isLead (boolean): True for the crew lead / foreman of this group. ## SetJobStatusRequest Body of `POST /api/v1/jobs/{id}/status`. Exactly one of `statusId` / `statusKey` is required. - statusId (integer (int32), nullable): Target status definition id (one of the org's statuses). - statusKey (string, nullable): Target status by name — a status definition name (case-insensitive) or a system status key such as `InProgress`, `Completed`. - note (string, nullable): Optional note appended to the status-change history entry. ## StockSummary Stock position of an item. - onHand (number (double)): Total on hand; same semantics as `onHand`. - byLocation (LocationStock[]): Per-location breakdown of physical rows. Sums to the row count, which may differ from OnHand for oversold or sub-unit items. ## UserSummaryDto A user of the calling organization as exposed to integrations by `GET /api/v1/users`: display name and schedule-group membership only. No credentials, security stamps or role details are ever returned. Requires the `scheduling` module. - id (string): User id (string GUID). Use it as an assignee id when scheduling jobs. - displayName (string): Display name, e.g. `"Sam Carter"`; falls back to the e-mail address when no name is recorded. - email (string, nullable): Login e-mail address; null when the account has none. - isActive (boolean): False when the account is deactivated (such users are only returned when `includeInactive=true`). - scheduleGroupIds (integer (int32)[]): Ids of the schedule groups the user belongs to (see `/api/v1/schedule-groups`). Empty when unassigned. ## ValidationProblemDetails RFC 7807 problem detail for 400 (malformed/invalid body) and 422 (semantically invalid) responses: the per-field messages are under errors. Also carries a traceId string to quote in support requests. - type (string, nullable): URI identifying the problem type (RFC 7807). Currently the generic reference for the HTTP status. - title (string, nullable): Short human-readable summary of the status, e.g. "Not Found". - status (integer (int32), nullable): HTTP status code, repeated from the response line. - detail (string, nullable): Explanation specific to this occurrence, e.g. "Job 42 was not found in this organization." - instance (string, nullable): Request path the problem occurred on, when known. - errors (object): Validation failures keyed by JSON field name in camelCase (an empty key holds body-level errors); each value is an array of messages, e.g. { "name": ["The Name field is required."] }.