Forz Public API V2 (2026-04-30)

Download OpenAPI specification:

REST API for Forz field-service management.

  • Authentication: API key (Bearer token) via /settings/api_keys. Format: fz_<UUIDv7>.
  • Errors: RFC 9457 application/problem+json with stable code field. See docs/api/v2/errors.md.
  • Pagination: HMAC-signed cursors via Link header. List responses wrap {data, has_more}.
  • Sorting, filtering & search: list endpoints accept ?sort=field (prefix - for descending), ?field=value / ?field[gte|lte|gt|lt|ne|in]=value filters, and ?q= free-text search. Each endpoint allowlists its own fields — see that endpoint's parameters. All three bind to the cursor: changing a filter or q mid-pagination returns cursor.invalid_filters, and sort is fixed by the cursor on continuation pages. Unknown field/operator → sort.invalid / filter.invalid (400).
  • Optimistic concurrency: ETag + If-Match on mutations.
  • Rate limits: RFC 9331 RateLimit-* headers on every response.
  • Idempotency: Idempotency-Key REQUIRED on financial POSTs (invoices, sales_orders).

me

Identity of the authenticated API key (account, user, key, api_version).

Identity of the authenticated key

Returns the account/company, user, API key (id + granted scopes) and resolved api_version that the presenting Bearer key maps to. Read-only and idempotent — the canonical "which tenant am I about to write to?" check before a mutation.

Requires only a valid key — no scope is needed, so a scopeless key can still introspect itself. The body carries no secrets (never the token, its hash, or its prefix) and no environment field (there is a single environment).

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

customers

Customer records

List customers

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across the customer's searchable fields (organization, number, status, reference, and the primary site's address). Narrows the result set; ordering still follows sort (not relevance). Binds to the cursor like any filter.

sort
string
Enum: "created_at" "-created_at" "updated_at" "-updated_at" "organization" "-organization" "number" "-number" "status" "-status"

Sort the list by a single field. Prefix with - for descending (e.g. -created_at). Ignored on continuation requests (the cursor keeps its original ordering). Unknown fields → 400 sort.invalid. Default: -created_at.

organization
string

Filter by exact organization name.

number
string

Filter by exact customer number.

string or object

Filter by status. Equality (?status=Active) or membership (?status[in]=Active,Lead).

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a customer

Authorizations:
bearerAuth
Request Body schema: application/json
required
organization
required
string

Display name. Must be unique per tenant (case-insensitive).

main_phone
string

Primary phone for the organization.

fax
string

Fax number.

website
string

Public website URL.

description
string

Free-text customer description.

assignee_id
integer <int64>

Assignee user ID. Surfaced as 'Assignee' in the Forz UI.

status
string

Tenant-configured status display_name. See docs/api/v2/README.md.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Customer. Replaces the full list (sets, not append). Unknown labels rejected with 422 validation.failed.

customer_notes
string

Internal rich-text notes (not surfaced on customer PDFs).

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=Customer. Unknown keys / wrong-type values are rejected with 422 validation.failed. Attachment-type fields are not writable via the JSON API.

Responses

Request samples

Content type
application/json
{
  • "organization": "Acme Plumbing Co.",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "assignee_id": 0,
  • "status": "Active",
  • "number": "string",
  • "labels": [
    ],
  • "customer_notes": "string",
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a customer

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a customer

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
organization
string

Display name. Must be unique per tenant.

main_phone
string

Primary phone for the organization.

fax
string

Fax number.

website
string

Public website URL.

description
string

Free-text customer description.

assignee_id
integer <int64>

Assignee user ID.

status
string

Tenant-configured status display_name.

labels
Array of strings

Replaces the full label list (sets, not append). Each value must match a Label display_name from GET /api/v2/labels?related_name=Customer. Send [] to clear all labels. Omit the key to leave the existing list untouched.

customer_notes
string

Internal rich-text notes.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's Customer template; unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Request samples

Content type
application/json
{
  • "organization": "Acme Plumbing Co.",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "assignee_id": 0,
  • "status": "string",
  • "labels": [
    ],
  • "customer_notes": "string",
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a customer (soft-delete via discard)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

sites

Customer sites

List sites

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: site name, address (street / city / state / zip), and the customer's organization. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a site

Authorizations:
bearerAuth
Request Body schema: application/json
required
site_name
required
string

Display name.

street
required
string

Street line 1.

street2
string

Street line 2 (optional).

city
required
string

City.

state
required
string

State / province.

zip_code
string

Postal / ZIP code.

description
string

Free-text description.

latitude
number

Override the auto-geocode result.

longitude
number

Override the auto-geocode result.

recurring_instructions
string

Standing instructions for jobs at this site.

object

Tenant-defined custom fields.

position
integer

Display order; defaults to end of list.

system_option_id
string <uuid>

FK to the SystemOption catalog row.

siteable_id
required
string

Parent Customer or Lead ID UUID v7 identifier.

siteable_type
required
string
Enum: "Customer" "Lead"

Class name of the parent record.

Responses

Request samples

Content type
application/json
{
  • "site_name": "string",
  • "street": "string",
  • "street2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "description": "string",
  • "latitude": 0,
  • "longitude": 0,
  • "recurring_instructions": "string",
  • "custom_fields": { },
  • "position": 0,
  • "system_option_id": "abbc4268-b361-493d-a39f-efc997227e78",
  • "siteable_id": "string",
  • "siteable_type": "Customer"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a site

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a site

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
site_name
string

Display name.

street
string

Street line 1.

street2
string

Street line 2.

city
string

City.

state
string

State / province.

zip_code
string

Postal / ZIP code.

description
string

Free-text description.

latitude
number

Geocoded latitude (override).

longitude
number

Geocoded longitude (override).

recurring_instructions
string

Standing instructions for jobs at this site.

object

Replaces tenant-defined custom fields.

position
integer

Display order.

system_option_id
string <uuid>

FK to the SystemOption catalog row.

Responses

Request samples

Content type
application/json
{
  • "site_name": "string",
  • "street": "string",
  • "street2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "description": "string",
  • "latitude": 0,
  • "longitude": 0,
  • "recurring_instructions": "string",
  • "custom_fields": { },
  • "position": 0,
  • "system_option_id": "abbc4268-b361-493d-a39f-efc997227e78"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a site

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

contacts

Site contacts

List contacts

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: first name, last name, email, and title. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a contact

Authorizations:
bearerAuth
Request Body schema: application/json
required
first_name
string

Given name.

last_name
string

Family name.

email
string <email>

Primary email.

phone
string

Office phone.

mobile
string

Mobile phone.

title
string

Job title.

contact_method
string

Preferred channel hint.

linkable_id
string

Parent record ID to attach to UUID v7 identifier.

linkable_type
string
Enum: "Customer" "Lead" "Site"

Parent record class name.

object

Tenant-defined custom field map.

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "email": "user@example.com",
  • "phone": "string",
  • "mobile": "string",
  • "title": "string",
  • "contact_method": "string",
  • "linkable_id": "string",
  • "linkable_type": "Customer",
  • "custom_fields": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a contact

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a contact

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
first_name
string

Given name.

last_name
string

Family name.

email
string <email>

Primary email.

phone
string

Office phone.

mobile
string

Mobile phone.

title
string

Job title.

contact_method
string

Preferred channel hint.

object

Replaces tenant-defined custom fields.

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "email": "user@example.com",
  • "phone": "string",
  • "mobile": "string",
  • "title": "string",
  • "contact_method": "string",
  • "custom_fields": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a contact

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

jobs

Field service jobs

List jobs

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, job type, status, reference, labels, the customer's organization, the site (name + address), the assignee's name, and the system type. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a job

Authorizations:
bearerAuth
Request Body schema: application/json
required
customer_id
string

Customer the job is for (UUID v7).

site_id
string

Site where the job is performed (UUID v7). Required unless the tenant set Job module AddressRequired = "No"; inherited from system_id when omitted. Must belong to customer_id.

contact_id
string

On-site Contact (UUID v7).

title
string

Short job title.

scope_of_work
string

Free-text scope of work (UI label: 'Job description').

work_description
string

Internal work description.

customer_notes
string

Internal customer notes.

reference
string

Partner-defined reference.

system_id
string

Specific System instance on the site (UUID v7). Required when the System module is enabled; sets the job site when site_id is omitted. Must belong to that site.

job_type
string

Tenant-configured job type.

priority
string

Priority string.

due_date
string <date>

Due date (plain calendar date).

schedule_date
string <date>

Scheduled date.

status
string

Tenant-configured Job status display_name.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Job. Replaces the full list. Unknown labels rejected with 422 validation.failed.

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=Job. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Array of objects (LineitemInput)

Initial lineitems for the job. Each entry creates a new line. Server populates discount_amount, tax_amount, line_total, tax_applied from the inputs.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "title": "string",
  • "scope_of_work": "string",
  • "work_description": "string",
  • "customer_notes": "string",
  • "reference": "string",
  • "system_id": "string",
  • "job_type": "string",
  • "priority": "string",
  • "due_date": "2019-08-24",
  • "schedule_date": "2019-08-24",
  • "status": "string",
  • "number": "string",
  • "labels": [
    ],
  • "custom_fields": {
    },
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a job

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a job

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
customer_id
string

Customer the job is for (UUID v7).

site_id
string

Site where the job is performed (UUID v7). Required unless the tenant set Job module AddressRequired = "No"; inherited from system_id when omitted. Must belong to customer_id.

contact_id
string

On-site Contact (UUID v7).

title
string

Short job title.

scope_of_work
string

Free-text scope of work.

work_description
string

Internal work description.

customer_notes
string

Internal customer notes.

reference
string

Partner-defined reference.

system_id
string

Specific System instance on the site (UUID v7). Required when the System module is enabled; sets the job site when site_id is omitted. Must belong to that site.

job_type
string

Tenant-configured job type.

priority
string

Priority string.

due_date
string <date>

Due date.

schedule_date
string <date>

Scheduled date.

status
string

Tenant-configured Job status.

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Job. Send [] to clear; omit to leave untouched.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's Job template.

Array of objects (LineitemInput)

Snapshot replace of the full lineitem list. Send id to update an existing line; omit id to create a new one. Any existing line whose id is not in the array is destroyed. Send [] to clear all lines; omit the key entirely to leave them untouched.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "title": "string",
  • "scope_of_work": "string",
  • "work_description": "string",
  • "customer_notes": "string",
  • "reference": "string",
  • "system_id": "string",
  • "job_type": "string",
  • "priority": "string",
  • "due_date": "2019-08-24",
  • "schedule_date": "2019-08-24",
  • "status": "string",
  • "labels": [
    ],
  • "custom_fields": {
    },
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a job

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

estimates

Estimates and quotes

List estimates

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, amount, status, and the customer's organization. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create an estimate

Authorizations:
bearerAuth
Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

title
string

Short estimate title.

description
string

Free-text description.

scope_of_work
string

Detailed scope of work.

estimate_date
string <date>

Issue date.

expiration_date
string <date>

Expiration date.

job_type
string

Tenant-configured job type.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Estimate. Replaces the full list. Unknown labels rejected with 422 validation.failed.

object

Tenant-defined custom field map.

public_notes
string

Public notes (visible on customer PDF).

tax_rate_id
string <uuid>

Tax rate ID.

status
string

Tenant-configured Estimate status.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

Array of objects (LineitemInput)

Initial estimate lineitems. Server populates computed totals.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "title": "string",
  • "description": "string",
  • "scope_of_work": "string",
  • "estimate_date": "2019-08-24",
  • "expiration_date": "2019-08-24",
  • "job_type": "string",
  • "system_id": "string",
  • "reference": "string",
  • "labels": [
    ],
  • "custom_fields": { },
  • "public_notes": "string",
  • "tax_rate_id": "6156135d-450b-464c-b854-039a7690a62e",
  • "status": "string",
  • "number": "string",
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get an estimate

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update an estimate

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

title
string

Short estimate title.

description
string

Free-text description.

scope_of_work
string

Detailed scope of work.

estimate_date
string <date>

Issue date.

expiration_date
string <date>

Expiration date.

job_type
string

Tenant-configured job type.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Estimate. Send [] to clear; omit to leave untouched.

object

Replaces tenant-defined custom fields.

public_notes
string

Public notes (visible on customer PDF).

tax_rate_id
string <uuid>

Tax rate ID.

status
string

Tenant-configured Estimate status.

Array of objects (LineitemInput)

Snapshot replace of the full lineitem list. Send id to update; omit id to create. Missing IDs are destroyed. Omit key to preserve.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "title": "string",
  • "description": "string",
  • "scope_of_work": "string",
  • "estimate_date": "2019-08-24",
  • "expiration_date": "2019-08-24",
  • "job_type": "string",
  • "system_id": "string",
  • "reference": "string",
  • "labels": [
    ],
  • "custom_fields": { },
  • "public_notes": "string",
  • "tax_rate_id": "6156135d-450b-464c-b854-039a7690a62e",
  • "status": "string",
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete an estimate

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

invoices

Invoices (financial; Idempotency-Key required on POST)

List invoices

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across the invoice's searchable fields (number, reference, amount, balance, status, and the customer's organization). Narrows the result set; ordering still follows sort (not relevance). Binds to the cursor like any filter.

sort
string
Enum: "created_at" "-created_at" "updated_at" "-updated_at" "invoice_date" "-invoice_date" "due_date" "-due_date" "number" "-number" "status" "-status"

Sort the list by a single field. Prefix with - for descending (e.g. -invoice_date). Ignored on continuation requests (the cursor keeps its original ordering). Unknown fields → 400 sort.invalid. Default: -created_at.

customer_id
string <uuid>

Filter to invoices for a given customer (UUID v7).

number
string

Filter by exact invoice number.

string or object

Filter by status. Equality (?status=open) or membership (?status[in]=open,overdue).

object

Filter by invoice date. Operators: gte, lte, gt, lt (ISO-8601 date).

object

Filter by due date. Operators: gte, lte, gt, lt (ISO-8601 date).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create an invoice

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
required
string [ 1 .. 255 ] characters

Required on financial POST per CONT-09. Missing → 400 problem+json idempotency_key.required. Same key + different body → 409 idempotency_key.in_use.

Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

payment_term_id
string <uuid>

PaymentTerm ID.

title
string

Short invoice title.

invoice_date
string <date>

Issue date.

due_date
string <date>

Due date.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Invoice. Replaces the full list. Unknown labels rejected with 422 validation.failed.

object

Tenant-defined custom field map.

public_notes
string

Public notes (visible on customer PDF).

customer_notes
string

Internal customer notes (not on PDF).

back_office_notes
string

Internal accounting notes (not on PDF).

tax_rate_id
string <uuid>

Tax rate ID.

po_number
string

Customer's PO number.

status
string

Tenant-configured Invoice status.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

Array of objects (LineitemInput)

Initial invoice lineitems. Server populates computed totals.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "payment_term_id": "d5e53127-7029-4c3e-a9e2-28cd951529f8",
  • "title": "string",
  • "invoice_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "system_id": "string",
  • "reference": "string",
  • "labels": [
    ],
  • "custom_fields": { },
  • "public_notes": "string",
  • "customer_notes": "string",
  • "back_office_notes": "string",
  • "tax_rate_id": "6156135d-450b-464c-b854-039a7690a62e",
  • "po_number": "string",
  • "status": "string",
  • "number": "string",
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get an invoice

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update an invoice

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

payment_term_id
string <uuid>

PaymentTerm ID.

title
string

Short invoice title.

invoice_date
string <date>

Issue date.

due_date
string <date>

Due date.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Invoice. Send [] to clear; omit to leave untouched.

object

Replaces tenant-defined custom fields.

public_notes
string

Public notes.

customer_notes
string

Internal customer notes.

back_office_notes
string

Internal accounting notes.

tax_rate_id
string <uuid>

Tax rate ID.

po_number
string

Customer's PO number.

status
string

Tenant-configured Invoice status.

Array of objects (LineitemInput)

Snapshot replace of the full lineitem list. Send id to update; omit id to create. Missing IDs are destroyed. Omit key to preserve.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "payment_term_id": "d5e53127-7029-4c3e-a9e2-28cd951529f8",
  • "title": "string",
  • "invoice_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "system_id": "string",
  • "reference": "string",
  • "labels": [
    ],
  • "custom_fields": { },
  • "public_notes": "string",
  • "customer_notes": "string",
  • "back_office_notes": "string",
  • "tax_rate_id": "6156135d-450b-464c-b854-039a7690a62e",
  • "po_number": "string",
  • "status": "string",
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete an invoice

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

sales_orders

Sales orders (financial; Idempotency-Key required on POST)

List sales orders

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, status, and the customer's organization. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a sales order

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
required
string [ 1 .. 255 ] characters

Required on financial POST per CONT-09. Missing → 400 problem+json idempotency_key.required. Same key + different body → 409 idempotency_key.in_use.

Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

payment_term_id
string <uuid>

PaymentTerm ID.

date
string <date>

Order date.

due_date
string <date>

Due date.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

status
string

Tenant-configured SalesOrder status.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=SalesOrder. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Array of objects (LineitemInput)

Initial sales-order lineitems. Server populates computed totals.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "payment_term_id": "d5e53127-7029-4c3e-a9e2-28cd951529f8",
  • "date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "system_id": "string",
  • "reference": "string",
  • "status": "string",
  • "number": "string",
  • "custom_fields": {
    },
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a sales order

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a sales order

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
customer_id
string

Customer ID (UUID v7).

site_id
string

Site ID (UUID v7). Service address — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id.

contact_id
string

Contact ID (UUID v7).

payment_term_id
string <uuid>

PaymentTerm ID.

date
string <date>

Order date.

due_date
string <date>

Due date.

system_id
string

System instance ID (UUID v7). Required when the System module is enabled and a site_id is set; must belong to that site.

reference
string

Partner-defined reference.

status
string

Tenant-configured SalesOrder status.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's SalesOrder template.

Array of objects (LineitemInput)

Snapshot replace of the full lineitem list. Send id to update; omit id to create. Missing IDs are destroyed. Omit key to preserve.

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "site_id": "string",
  • "contact_id": "string",
  • "payment_term_id": "d5e53127-7029-4c3e-a9e2-28cd951529f8",
  • "date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "system_id": "string",
  • "reference": "string",
  • "status": "string",
  • "custom_fields": {
    },
  • "lineitems": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a sales order

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

items

Catalog items

List items

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: name, SKU, location code, unit cost, unit price, item type, number, and the category/subcategory names. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create an item

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string

Display name.

description
string

Free-text description.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

item_sku
string

Stock keeping unit.

item_type
string
Enum: "Inventory" "NonInventory" "Service"

Type of item. Must be one of the three allowed values (matches the web). 'Inventory' tracks stock; 'Service' relaxes the unit requirement. Unknown values are rejected with 422 validation.failed.

unit
string

Unit of measure ('pcs', 'lb', 'ft', 'hr', etc.).

location_code
string

Default bin / shelf code.

active
boolean

Active in pickers.

taxable
boolean

Subject to tax.

reorder_point
number

Reorder threshold (item-level default).

item_category_id
string <uuid>

ItemCategory FK.

item_subcategory_id
string <uuid>

ItemSubcategory FK.

income_account_id
string <uuid>

QBO income GL account.

expense_account_id
string <uuid>

QBO expense GL account.

inventory_asset_account_id
string <uuid>

QBO inventory asset GL account.

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=Item. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "number": "string",
  • "item_sku": "string",
  • "item_type": "Inventory",
  • "unit": "string",
  • "location_code": "string",
  • "active": true,
  • "taxable": true,
  • "reorder_point": 0,
  • "item_category_id": "b40c2bb0-da8f-44b4-8f96-939fb5329d88",
  • "item_subcategory_id": "6c524674-237d-49ad-8c20-c3bc4cb8ddf0",
  • "income_account_id": "120da5b6-0cd6-45ee-b703-6fe22e631416",
  • "expense_account_id": "f5ceaf7b-31e3-413d-9069-4d4a3986f549",
  • "inventory_asset_account_id": "00f363b5-de3b-47c2-8ffa-e8d84d916100",
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get an item

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update an item

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
name
string

Display name.

description
string

Free-text description.

number
string

Item number.

item_sku
string

SKU.

item_type
string
Enum: "Inventory" "NonInventory" "Service"

Type of item. Must be one of the three allowed values (matches the web). Unknown values are rejected with 422 validation.failed.

unit
string

Unit of measure.

location_code
string

Default bin / shelf code.

active
boolean

Active in pickers.

taxable
boolean

Subject to tax.

reorder_point
number

Reorder threshold.

item_category_id
string <uuid>

ItemCategory FK.

item_subcategory_id
string <uuid>

ItemSubcategory FK.

income_account_id
string <uuid>

QBO income GL account.

expense_account_id
string <uuid>

QBO expense GL account.

inventory_asset_account_id
string <uuid>

QBO inventory asset GL account.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's Item template.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "number": "string",
  • "item_sku": "string",
  • "item_type": "Inventory",
  • "unit": "string",
  • "location_code": "string",
  • "active": true,
  • "taxable": true,
  • "reorder_point": 0,
  • "item_category_id": "b40c2bb0-da8f-44b4-8f96-939fb5329d88",
  • "item_subcategory_id": "6c524674-237d-49ad-8c20-c3bc4cb8ddf0",
  • "income_account_id": "120da5b6-0cd6-45ee-b703-6fe22e631416",
  • "expense_account_id": "f5ceaf7b-31e3-413d-9069-4d4a3986f549",
  • "inventory_asset_account_id": "00f363b5-de3b-47c2-8ffa-e8d84d916100",
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete an item

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

tasks

Job and project tasks

List tasks

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, status, title, description, and the assignee's name. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a task

Authorizations:
bearerAuth
Request Body schema: application/json
required
title
required
string

Short task title.

description
string

Free-text description.

status
string

Tenant-configured Task status.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

position
integer

Display order; defaults to end of column.

due_date
string <date>

Due date.

taskable_type
string
Enum: "Customer" "Job" "Lead" "Project" "SalesOrder" "Invoice" "Estimate"

Parent record class name.

taskable_id
string

Parent record ID (UUID v7).

user_id
integer <int64>

Legacy assignee alias — prefer assignee_id.

assignee_id
integer <int64>

Assignee user ID.

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "status": "string",
  • "number": "string",
  • "position": 0,
  • "due_date": "2019-08-24",
  • "taskable_type": "Customer",
  • "taskable_id": "string",
  • "user_id": 0,
  • "assignee_id": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a task

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a task

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
title
string

Short task title.

description
string

Free-text description.

status
string

Tenant-configured Task status. Setting a 'completed' status sets completed_at.

position
integer

Display order.

due_date
string <date>

Due date.

taskable_type
string
Enum: "Customer" "Job" "Lead" "Project" "SalesOrder" "Invoice" "Estimate"

Parent record class name.

taskable_id
string

Parent record ID (UUID v7).

user_id
integer <int64>

Legacy assignee alias — prefer assignee_id.

assignee_id
integer <int64>

Assignee user ID.

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "status": "string",
  • "position": 0,
  • "due_date": "2019-08-24",
  • "taskable_type": "Customer",
  • "taskable_id": "string",
  • "user_id": 0,
  • "assignee_id": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a task

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

leads

CRM leads

List leads

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, status, organization, industry, labels, the assignee's name, and the site address. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a lead

Authorizations:
bearerAuth
Request Body schema: application/json
required
organization
required
string

Display name. Unique per tenant.

industry
string

Free-text industry label.

main_phone
string

Primary phone.

fax
string

Fax number.

website
string

Website URL.

description
string

Free-text lead description.

assignee_id
integer <int64>

Assignee user ID.

status
string

Tenant-configured Lead status display_name.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Lead. Replaces the full list. Unknown labels rejected with 422 validation.failed.

lead_notes
string

Internal rich-text notes.

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=Lead. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Request samples

Content type
application/json
{
  • "organization": "Lead Co.",
  • "industry": "string",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "assignee_id": 0,
  • "status": "New",
  • "number": "string",
  • "labels": [
    ],
  • "lead_notes": "string",
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a lead

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a lead

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
organization
string

Display name.

industry
string

Free-text industry label.

main_phone
string

Primary phone.

fax
string

Fax number.

website
string

Website URL.

description
string

Free-text lead description.

assignee_id
integer <int64>

Assignee user ID.

status
string

Lead status. Set to 'Converted' to trigger the Lead → Customer conversion.

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Lead. Send [] to clear; omit to leave untouched.

lead_notes
string

Internal rich-text notes.

position
integer

Pipeline ordering within the lead's status column.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's Lead template.

Responses

Request samples

Content type
application/json
{
  • "organization": "string",
  • "industry": "string",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "assignee_id": 0,
  • "status": "string",
  • "labels": [
    ],
  • "lead_notes": "string",
  • "position": 0,
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a lead

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

deals

CRM deals

List deals

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, name, status, labels, source, description, the customer's/lead's organization, and the assignee's name. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a deal

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string

Short deal name.

status
string

Tenant-configured Deal status / pipeline stage.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

description
string

Free-text description.

source
string

Lead source / channel.

probability
number

Win probability percentage 0–100.

close_date
string <date>

Expected close date.

customer_id
string <uuid>

Customer FK UUID v7 (mutually exclusive with lead_id).

lead_id
string <uuid>

Lead FK UUID v7 (mutually exclusive with customer_id).

assignee_id
integer <int64>

Assignee user ID.

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Deal. Replaces the full list. Unknown labels rejected with 422 validation.failed.

object

Tenant-defined custom field values. Keys are stringified cf_template_item.id integers from GET /api/v2/custom_field_definitions?related_name=Deal. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "status": "string",
  • "number": "string",
  • "description": "string",
  • "source": "string",
  • "probability": 0,
  • "close_date": "2019-08-24",
  • "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
  • "lead_id": "9bddab70-98e6-43a9-8f32-c9788b9de0c0",
  • "assignee_id": 0,
  • "labels": [
    ],
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a deal

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a deal

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
name
string

Short deal name.

status
string

Tenant-configured Deal status.

description
string

Free-text description.

source
string

Lead source.

probability
number

Win probability percentage 0–100.

close_date
string <date>

Expected close date.

customer_id
string

Customer FK (UUID v7).

lead_id
string

Lead FK (UUID v7).

assignee_id
integer <int64>

Assignee user ID.

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Deal. Send [] to clear; omit to leave untouched.

object

Merges into existing custom fields — keys not in the payload are preserved. Set a key to null to clear it. Validated against the tenant's Deal template.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "status": "string",
  • "description": "string",
  • "source": "string",
  • "probability": 0,
  • "close_date": "2019-08-24",
  • "customer_id": "string",
  • "lead_id": "string",
  • "assignee_id": 0,
  • "labels": [
    ],
  • "custom_fields": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a deal

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

projects

Projects

List projects

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

q
string

Free-text search across: number, name, status, project type, the customer's organization, the site address, and the contact's name. Narrows results; ordering follows sort (not relevance). Binds to the cursor like a filter.

sort
string

Sort by a single field; prefix with - for descending (e.g. -created_at). Allowed fields vary per endpoint — an unknown field returns 400 sort.invalid. Honored on the first page only; on cursor continuation pages the cursor's original ordering is kept.

object

Filter by creation time. Operators: gte, lte, gt, lt (ISO-8601).

object

Filter by last-update time. Operators: gte, lte, gt, lt (ISO-8601).

string or object

Filter by status. Equality (?status=Open) or membership (?status[in]=Open,Closed).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Create a project

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string

Short project name.

status
string

Tenant-configured Project status.

number
string

Optional document number. When omitted the server auto-generates a sequential value; when supplied it is respected and must be unique per tenant. Immutable after creation (cannot be changed via PATCH).

description
string

Free-text description.

project_type
string
Enum: "customer" "internal"

Required (mirrors the web's New-project chooser). 'customer' needs customer_id and a service address; 'internal' needs only a name. Unknown values are rejected with 422 validation.failed.

customer_id
string

Customer FK (UUID v7). Required for customer-type projects; omitted for internal projects.

site_id
string

Site FK (UUID v7). Service address for customer-type projects — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id. Not used by internal projects; projects have no system field.

assignee_id
integer <int64>

Assignee (project manager) user ID.

contact_id
string

Primary Contact FK (UUID v7).

user_ids
Array of integers <int64> [ items <int64 > ]

Project team — array of User IDs (HABTM).

labels
Array of strings

Tenant-defined labels to apply. Each value must match a Label display_name from GET /api/v2/labels?related_name=Project. Replaces the full list. Unknown labels rejected with 422 validation.failed.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "status": "string",
  • "number": "string",
  • "description": "string",
  • "project_type": "customer",
  • "customer_id": "string",
  • "site_id": "string",
  • "assignee_id": 0,
  • "contact_id": "string",
  • "user_ids": [
    ],
  • "labels": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a project

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a project

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Request Body schema: application/json
required
name
string

Short project name.

status
string

Tenant-configured Project status.

description
string

Free-text description.

project_type
string
Enum: "customer" "internal"

'customer' or 'internal'. The web sets it at creation and does not change it afterwards; unknown values are rejected with 422 validation.failed.

customer_id
string

Customer FK (UUID v7). Required for customer-type projects.

site_id
string

Site FK (UUID v7). Service address for customer-type projects — required unless the tenant set Job module AddressRequired = "No". Must belong to customer_id. Projects have no system field.

assignee_id
integer <int64>

Assignee user ID.

contact_id
string

Primary Contact FK (UUID v7).

user_ids
Array of integers <int64> [ items <int64 > ]

Replaces the project team (HABTM).

labels
Array of strings

Replaces the full label list. Each value must match a Label display_name from GET /api/v2/labels?related_name=Project. Send [] to clear; omit to leave untouched.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "status": "string",
  • "description": "string",
  • "project_type": "customer",
  • "customer_id": "string",
  • "site_id": "string",
  • "assignee_id": 0,
  • "contact_id": "string",
  • "user_ids": [
    ],
  • "labels": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a project

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

header Parameters
If-Match
required
string^W/"[0-9]+-[0-9]+"$

Weak ETag from a prior GET on this resource. Mismatched ETag emits 412 Precondition Failed; missing header on PATCH/DELETE emits 428 Precondition Required.

Responses

Response samples

Content type
application/problem+json
{}

lookups

Read-only reference catalogs (payment terms, tax rates, job types, item categories, system options, labels). Resolve FKs and validate categorical fields on other resources.

List payment terms

Returns the tenant's catalog of PaymentTerm rows (Net-15, Net-30, custom, etc.). Use the id returned here to set Invoice.payment_term_id and SalesOrder.payment_term_id. Archived rows are filtered out.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List tax rates

Returns the tenant's catalog of TaxRate rows. Use the id returned here to set tax_rate_id on Invoice / Estimate / Job / Lineitem. Archived and discarded rows are filtered out.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List job types

Returns the tenant's catalog of JobType rows. Use the display_name returned here as the value to post for Job.job_type / Estimate.job_type (those fields are free-form strings backed by this catalog). Discarded rows are filtered out.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List item categories

Returns the tenant's catalog of ItemCategory rows. Use the id returned here to set Item.item_category_id and Item.item_subcategory_id. Subcategories are modeled via the parent_item_category_id self-FK — categories with that field set are subcategories of the parent.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List system options

Returns the tenant's catalog of SystemOption rows (Fire Alarm, HVAC, Sprinkler, etc.). Use the id returned here to set Site.system_option_id. has_device_inventory distinguishes systems that track devices (e.g. extinguishers) from those that don't. Discarded rows are filtered out.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List labels

Returns the tenant's catalog of Label rows. Labels are tenant-defined per-resource categorical tags — the related_name column constrains which resource type each label applies to (Customer / Lead / Job / Deal / Estimate / Invoice / Project).

Use the display_name returned here as the value to put in the labels: [] array on the parent resource. Partners write strings, not ids — labels are stored as display_name strings in the JSONB column on every parent record, and label renames are migrated to existing records by the server (LabelsUpdateRelatedJob).

Filter to one resource type via ?related_name=Customer.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

related_name
string
Enum: "Customer" "Lead" "Job" "Deal" "Estimate" "Invoice" "Project"

Filter to a single resource type's label catalog. Omit to list all.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

List statuses

Returns the tenant's catalog of StatusOption rows. Statuses are tenant-configurable per resource type — the related_name column constrains which resource each status applies to (Customer / Job / Estimate / Invoice / Lead / Deal / Project / Task / ...).

Use the display_name returned here as the value to put in the status field on the parent resource. Writes are validated against these values; an unknown status is rejected with 422 validation.failed.

Filter to one resource type via ?related_name=Customer.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

related_name
string

Filter to a single resource type's status catalog (e.g. Customer, Job, Estimate). Omit to list all.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

custom_field_definitions

Read-only discovery of tenant-defined custom-field templates and their field definitions. Use to interpret the custom_fields JSONB on Customer, Lead, Job, Estimate, Invoice, and other resources.

List custom field definitions

Returns the tenant's catalog of custom-field definition groups — one per module (Customer, Lead, Job, Estimate, Invoice, etc.) plus optional SystemOption-scoped sub-groups (Device, DeviceInspection).

Use the id of each entry inside fields[] as the JSONB key when reading or writing the custom_fields blob on the parent resource. For example, a Customer with custom_fields: { "0190a1f2-1c3d-7e4f-8a9b-2c3d4e5f6a7b": "blue" } means that field id (e.g. "Preferred color") is set to "blue".

Filter by module via ?related_name=Customer (case-sensitive, matches the group's related_name — see the enum on CustomFieldDefinitionGroup.related_name).

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque HMAC-signed pagination cursor. Omit on the first page; partners pass through verbatim — they MUST NOT decode or modify it (the signing secret rotates annually per D-21).

limit
integer [ 1 .. 100 ]
Default: 25

Maximum number of items per page. Capped at 100 (CONT-07); requesting more emits 400 problem+json pagination.limit_too_large.

related_name
string
Enum: "Customer" "Contact" "Job" "SalesOrder" "Invoice" "PurchaseOrder" "Bill" "Estimate" "Site" "Item" "Lead" "Deal" "Device" "DeviceInspection" "SystemOption"

Filter to a single module's definition group(s). Omit to list all.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true
}

Get a custom field definition group

Returns a single definition group by id, including its ordered list of field definitions. 404 if the id does not belong to the authenticated tenant.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Custom field definition group id.

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "display_name": "Customer",
  • "related_name": "Customer",
  • "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef",
  • "fields": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}