Forz Public API V2 (2026-04-30)

Spec revision aee5b4e, published 2026-10-01.

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 the error code catalog.
  • Changelog: additive changes are listed in the changelog; breaking changes ship as a new dated version.
  • 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|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 query parameter, filter field or operator → filter.invalid (400); unknown sort field → sort.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 Headers
Cache-Control
string

Always private, no-store — an auth-scoped identity body must not be shared-cached.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Customer)
has_more
required
boolean

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.

reference
string

Partner-defined reference.

assignee_id
integer <int64>

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

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 custom-field definition UUIDs (fields[].id) 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 set with PUT /api/v2/customers/{id}/custom_fields/{field_id} (multipart).

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Customer)

A customer (org / company / individual) in the tenant's CRM. Contact and address data live on linked Contact and Site records (via the linkable_* and siteable_* polymorphic FKs respectively); the Customer record itself is the parent identity + ownership/status metadata.

Request samples

Content type
application/json
{
  • "organization": "Acme Plumbing Co.",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "reference": "string",
  • "assignee_id": 0,
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Customer)

A customer (org / company / individual) in the tenant's CRM. Contact and address data live on linked Contact and Site records (via the linkable_* and siteable_* polymorphic FKs respectively); the Customer record itself is the parent identity + ownership/status metadata.

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.

reference
string

Partner-defined reference.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Customer)

A customer (org / company / individual) in the tenant's CRM. Contact and address data live on linked Contact and Site records (via the linkable_* and siteable_* polymorphic FKs respectively); the Customer record itself is the parent identity + ownership/status metadata.

Request samples

Content type
application/json
{
  • "organization": "Acme Plumbing Co.",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "reference": "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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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).

customer_id
string <uuid>

Sites owned by this customer (siteable_type=Customer, siteable_id=). Equality only.

lead_id
string <uuid>

Sites owned by this lead (siteable_type=Lead, siteable_id=). Equality only.

Responses

Response Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Site)
has_more
required
boolean

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Site)

A physical service location attached to a Customer or Lead via the polymorphic siteable_* FK pair. Each Customer/Lead may have many sites; the first site is treated as the primary billing address by downstream integrations (QuickBooks, etc.).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Site)

A physical service location attached to a Customer or Lead via the polymorphic siteable_* FK pair. Each Customer/Lead may have many sites; the first site is treated as the primary billing address by downstream integrations (QuickBooks, etc.).

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

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 Site template; unknown keys / wrong-type values are rejected with 422 validation.failed.

position
integer

Display order.

system_option_id
string <uuid>

FK to the SystemOption catalog row.

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Site)

A physical service location attached to a Customer or Lead via the polymorphic siteable_* FK pair. Each Customer/Lead may have many sites; the first site is treated as the primary billing address by downstream integrations (QuickBooks, etc.).

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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Contact)
has_more
required
boolean

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.

Array of objects (PhoneNumberInput)

Initial phone numbers. Supplying this suppresses the legacy mobile auto-spawn.

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Contact)

A person attached to one or more Customer / Lead / Site records via the contact_linkages join table. The polymorphic linkable_* pair in the wire payload is the contact's main linkage (one where it is the record's primary, else its oldest). A contact can be linked to many records; list them with GET /api/v2/contacts/{id}/linkages.

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": { },
  • "phone_numbers": [
    ]
}

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Contact)

A person attached to one or more Customer / Lead / Site records via the contact_linkages join table. The polymorphic linkable_* pair in the wire payload is the contact's main linkage (one where it is the record's primary, else its oldest). A contact can be linked to many records; list them with GET /api/v2/contacts/{id}/linkages.

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

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 Contact template; unknown keys / wrong-type values are rejected with 422 validation.failed.

Array of objects (PhoneNumberInput)

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

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Contact)

A person attached to one or more Customer / Lead / Site records via the contact_linkages join table. The polymorphic linkable_* pair in the wire payload is the contact's main linkage (one where it is the record's primary, else its oldest). A contact can be linked to many records; list them with GET /api/v2/contacts/{id}/linkages.

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": { },
  • "phone_numbers": [
    ]
}

Response samples

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

Delete a contact

Refused with 409 contact.has_linkages while the contact is linked to any record; unlink it first.

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
{}

List a contact's linkages

Every Customer / Lead / Site this contact is linked to, oldest first. Not paginated.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

The contact's UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (ContactLinkage)

Response samples

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

Link a contact to a record

Links an existing contact to a Customer, Lead or Site. The first contact linked to a record becomes its primary; later ones don't unless primary: true is sent, which moves the record's primary to this contact.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

The contact's UUID v7 identifier.

Request Body schema: application/json
required
linkable_id
required
string <uuid>

The record to link to.

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

The record's class name.

relationship_type
string
Enum: "associated" "billing" "technical" "decision_maker"

Defaults to associated.

primary
boolean

Make this contact the record's primary. The first contact linked to a record is primary regardless.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (ContactLinkage)

One link between a contact and a Customer, Lead or Site.

Request samples

Content type
application/json
{
  • "linkable_id": "a45259cf-641e-4d02-8ce9-41e8763e7643",
  • "linkable_type": "Customer",
  • "relationship_type": "associated",
  • "primary": true
}

Response samples

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

Update a linkage

Changes the relationship type, or makes this contact the record's primary (primary: true demotes the previous primary). A primary can't be cleared with primary: false; make another contact primary instead.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

The contact's UUID v7 identifier.

linkage_id
required
string <uuid>

The linkage's UUID v7 identifier.

Request Body schema: application/json
required
relationship_type
string
Enum: "associated" "billing" "technical" "decision_maker"

The contact's role on this record.

primary
boolean

true makes this contact the record's primary. false is only accepted on a linkage that isn't primary.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (ContactLinkage)

One link between a contact and a Customer, Lead or Site.

Request samples

Content type
application/json
{
  • "relationship_type": "associated",
  • "primary": true
}

Response samples

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

Unlink a contact from a record

Removes the link; the contact itself stays. Refused with 409 contact.primary_linkage while this contact is the record's primary. To move a contact, link it to the new record, then unlink the old one.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

The contact's UUID v7 identifier.

linkage_id
required
string <uuid>

The linkage's UUID v7 identifier.

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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Job)
has_more
required
boolean

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).

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 custom-field definition UUIDs (fields[].id) 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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Job)

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",
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Job)

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Job)

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",
  • "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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Estimate)
has_more
required
boolean

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.

customer_notes
string

Customer-facing notes shown on the estimate PDF.

tax_rate_id
string <uuid>

Tax rate ID.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Estimate)

A pre-sale quote / proposal sent to a Customer. Lineitems carry parts/ labor/services; the top-level amount is the server-computed total. On acceptance, an Estimate typically converts to a Job (workorderable_*) and/or an Invoice.

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": { },
  • "customer_notes": "string",
  • "tax_rate_id": "6156135d-450b-464c-b854-039a7690a62e",
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Estimate)

A pre-sale quote / proposal sent to a Customer. Lineitems carry parts/ labor/services; the top-level amount is the server-computed total. On acceptance, an Estimate typically converts to a Job (workorderable_*) and/or an Invoice.

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

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 Estimate template; unknown keys / wrong-type values are rejected with 422 validation.failed.

customer_notes
string

Customer-facing notes shown on the estimate 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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Estimate)

A pre-sale quote / proposal sent to a Customer. Lineitems carry parts/ labor/services; the top-level amount is the server-computed total. On acceptance, an Estimate typically converts to a Job (workorderable_*) and/or an Invoice.

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": { },
  • "customer_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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Invoice)
has_more
required
boolean

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Invoice)

A billable invoice issued to a Customer. Lineitems carry parts/labor/ services; amount and balance are server-computed money_field totals. Carries three audience-distinct notes fields — see docs/api/v2/README.md for which is visible on PDFs.

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",
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Invoice)

A billable invoice issued to a Customer. Lineitems carry parts/labor/ services; amount and balance are server-computed money_field totals. Carries three audience-distinct notes fields — see docs/api/v2/README.md for which is visible on PDFs.

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

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 Invoice template; unknown keys / wrong-type values are rejected with 422 validation.failed.

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Invoice)

A billable invoice issued to a Customer. Lineitems carry parts/labor/ services; amount and balance are server-computed money_field totals. Carries three audience-distinct notes fields — see docs/api/v2/README.md for which is visible on PDFs.

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",
  • "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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (SalesOrder)
has_more
required
boolean

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.

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 custom-field definition UUIDs (fields[].id) 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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (SalesOrder)

A confirmed sale, sitting between Estimate and Invoice in the accounting flow. SalesOrders are intentionally minimal at the header level — line totals roll up from lineitems; there is no top-level monetary aggregate (per Phase 1.0 baseline; CONT-13 may add one).

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",
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (SalesOrder)

A confirmed sale, sitting between Estimate and Invoice in the accounting flow. SalesOrders are intentionally minimal at the header level — line totals roll up from lineitems; there is no top-level monetary aggregate (per Phase 1.0 baseline; CONT-13 may add one).

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (SalesOrder)

A confirmed sale, sitting between Estimate and Invoice in the accounting flow. SalesOrders are intentionally minimal at the header level — line totals roll up from lineitems; there is no top-level monetary aggregate (per Phase 1.0 baseline; CONT-13 may add one).

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",
  • "custom_fields": {
    },
  • "lineitems": [
    ]
}

Response samples

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

Delete a sales order (soft-delete to the trash bin)

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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Item)
has_more
required
boolean

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. Changing it requires the toggle-active permission (403 auth.permission_denied otherwise).

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 custom-field definition UUIDs (fields[].id) from GET /api/v2/custom_field_definitions?related_name=Item. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Item)

A catalog item (inventoried part, service, or non-inventory good). inventory_levels and total_quantity are server-derived per InventoryLocation. The *_account_id fields map to QuickBooks GL accounts and are only meaningful for QBO-integrated tenants.

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Item)

A catalog item (inventoried part, service, or non-inventory good). inventory_levels and total_quantity are server-derived per InventoryLocation. The *_account_id fields map to QuickBooks GL accounts and are only meaningful for QBO-integrated tenants.

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. Changing it requires the toggle-active permission (403 auth.permission_denied otherwise).

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Item)

A catalog item (inventoried part, service, or non-inventory good). inventory_levels and total_quantity are server-derived per InventoryLocation. The *_account_id fields map to QuickBooks GL accounts and are only meaningful for QBO-integrated tenants.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Task)
has_more
required
boolean

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.

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).

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Task)

A todo / follow-up tied to a parent record (Customer, Job, Lead, etc.) via the polymorphic taskable_* FK pair. Carries three User FKs — creator_id is who created the task, assignee_id is the current owner, and user_id is a legacy alias for the assignee retained for backwards compatibility.

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "number": "string",
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Task)

A todo / follow-up tied to a parent record (Customer, Job, Lead, etc.) via the polymorphic taskable_* FK pair. Carries three User FKs — creator_id is who created the task, assignee_id is the current owner, and user_id is a legacy alias for the assignee retained for backwards compatibility.

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Task)

A todo / follow-up tied to a parent record (Customer, Job, Lead, etc.) via the polymorphic taskable_* FK pair. Carries three User FKs — creator_id is who created the task, assignee_id is the current owner, and user_id is a legacy alias for the assignee retained for backwards compatibility.

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "status": "string",
  • "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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Lead)
has_more
required
boolean

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.

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 custom-field definition UUIDs (fields[].id) from GET /api/v2/custom_field_definitions?related_name=Lead. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Lead)

A pre-conversion sales prospect. Mirror of Customer with a pipeline- oriented status flow ("New" → "Contacted" → "Qualified" → "Converted" / "Lost"). Converting a lead to a Customer is not exposed via v2 — setting status only changes the status.

Request samples

Content type
application/json
{
  • "organization": "Lead Co.",
  • "industry": "string",
  • "main_phone": "string",
  • "fax": "string",
  • "website": "string",
  • "description": "string",
  • "assignee_id": 0,
  • "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Lead)

A pre-conversion sales prospect. Mirror of Customer with a pipeline- oriented status flow ("New" → "Contacted" → "Qualified" → "Converted" / "Lost"). Converting a lead to a Customer is not exposed via v2 — setting status only changes the status.

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

Tenant-configured Lead status display_name.

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.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Lead)

A pre-conversion sales prospect. Mirror of Customer with a pipeline- oriented status flow ("New" → "Contacted" → "Qualified" → "Converted" / "Lost"). Converting a lead to a Customer is not exposed via v2 — setting status only changes the status.

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",
  • "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.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Deal)
has_more
required
boolean

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.

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 custom-field definition UUIDs (fields[].id) from GET /api/v2/custom_field_definitions?related_name=Deal. Unknown keys / wrong-type values are rejected with 422 validation.failed.

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Deal)

A sales opportunity attached to either a Customer or a Lead (mutually exclusive — pre-conversion deals carry lead_id, post-conversion deals carry customer_id). Used to forecast revenue with amount × probability.

Request samples

Content type
application/json
{
  • "name": "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Deal)

A sales opportunity attached to either a Customer or a Lead (mutually exclusive — pre-conversion deals carry lead_id, post-conversion deals carry customer_id). Used to forecast revenue with amount × probability.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Deal)

A sales opportunity attached to either a Customer or a Lead (mutually exclusive — pre-conversion deals carry lead_id, post-conversion deals carry customer_id). Used to forecast revenue with amount × probability.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Project)
has_more
required
boolean

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.

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). An id that is not a user in this account is a 422 on user_ids.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Project)

A long-lived multi-job engagement (think: kitchen remodel, building retrofit). Aggregates Jobs, Estimates, Invoices, etc. under a single identity. amount is the total contract value, not a server-computed sum — partners set it explicitly.

Request samples

Content type
application/json
{
  • "name": "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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Project)

A long-lived multi-job engagement (think: kitchen remodel, building retrofit). Aggregates Jobs, Estimates, Invoices, etc. under a single identity. amount is the total contract value, not a server-computed sum — partners set it explicitly.

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). An id that is not a user in this account is a 422 on user_ids; the update moves the ETag.

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

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Project)

A long-lived multi-job engagement (think: kitchen remodel, building retrofit). Aggregates Jobs, Estimates, Invoices, etc. under a single identity. amount is the total contract value, not a server-computed sum — partners set it explicitly.

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
{}

tools

Tracked equipment / tools assigned to technicians (read-only)

List tools

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Tool)
has_more
required
boolean

Response samples

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

Get a tool

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Tool)

Tracked equipment / tools assigned to technicians. Read-only via the API; comments at /api/v2/tools/{id}/notes.

Response samples

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

vendors

Suppliers referenced by purchase orders and item costs (read-only)

List vendors

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Vendor)
has_more
required
boolean

Response samples

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

Get a vendor

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Vendor)

Suppliers referenced by purchase orders and item costs. Read-only via the API; comments at /api/v2/vendors/{id}/notes.

Response samples

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

tickets

Support tickets raised against a customer / site / system (read-only)

List tickets

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Ticket)
has_more
required
boolean

Response samples

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

Get a ticket

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Ticket)

Support tickets raised against a customer / site / system. Read-only via the API; comments at /api/v2/tickets/{id}/notes.

Response samples

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

purchase_orders

Purchase orders issued to vendors (read-only)

List purchase 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.

Responses

Response Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (PurchaseOrder)
has_more
required
boolean

Response samples

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

Get a purchase order

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (PurchaseOrder)

Purchase orders issued to vendors. Line items are not exposed yet. Read-only via the API; comments at /api/v2/purchase_orders/{id}/notes.

Response samples

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

recurring_jobs

Recurring job schedules that generate jobs on next_date (read-only)

List recurring 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.

Responses

Response Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (RecurringJob)
has_more
required
boolean

Response samples

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

Get a recurring job

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (RecurringJob)

Recurring job schedules that generate jobs on next_date. Read-only via the API; comments at /api/v2/recurring_jobs/{id}/notes.

Response samples

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

recurring_invoices

Recurring invoice schedules that generate invoices on next_date (read-only)

List recurring 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.

Responses

Response Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (RecurringInvoice)
has_more
required
boolean

Response samples

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

Get a recurring invoice

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Server-generated UUID v7 identifier.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (RecurringInvoice)

Recurring invoice schedules that generate invoices on next_date. Read-only via the API; comments at /api/v2/recurring_invoices/{id}/notes.

Response samples

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

notes

User-authored comments on a record (/api/v2//{id}/notes). Gated by the parent resource's scopes.

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

List a record's comments

User-authored comments on the parent record, newest first. System activity entries (record created, viewed, address changed, …) are excluded. Requires the parent resource's <resource>:read scope (e.g. jobs:read for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

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 Headers
Link
string

RFC 5988 link to next page. Format: <next_url>; rel="next". Absent when has_more is false.

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Note)
has_more
required
boolean

Response samples

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

Add a comment to a record

Requires the parent resource's <resource>:write scope (e.g. jobs:write for /api/v2/jobs/{id}/notes).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Parent record UUID v7.

Request Body schema: application/json
required
required
object

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (Note)

A user-authored comment on a record (the web "Comments" tab).

Request samples

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

Response samples

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

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (PaymentTerm)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (TaxRate)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (JobType)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (ItemCategory)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (SystemOption)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (Label)
has_more
required
boolean

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 status.transition_invalid.

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (StatusOption)
has_more
required
boolean

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.

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Upload a file into an attachment-type custom field

Stores the uploaded file and sets the field to {"url", "filename"} (the same shape the web app writes), replacing any previous value. Only an uploaded file is accepted — a URL cannot be set as the value. Any content type; maximum 10 MB. Requires the resource's <resource>:write scope and If-Match, like PATCH. Other field types are written with PATCH custom_fields (422 here).

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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: multipart/form-data
required
file
required
string <binary>

Responses

Response Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

Clear an attachment-type custom field

Removes the field's key from custom_fields. Same scope and If-Match rules as PUT.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Record UUID v7.

field_id
required
string <uuid>

Attachment-type custom field definition id (GET /api/v2/custom_field_definitions).

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 Headers
ETag
string^W/"[0-9]+-[0-9]+"$
Examples: "W/\"1745596800-3\""

Weak ETag — W/"<updated_at_epoch>-<lock_version>". Partners pass the value verbatim back as If-Match on PATCH/DELETE. Any update that supplies a child list (lineitems, phone_numbers, project user_ids) moves the ETag, even when no other field changes. An unparseable date/date-time value (e.g. due_date: "garbage") is a 422 validation.failed naming the field, on create and update; send null to clear a date. A non-object under the resource key (e.g. {"task": "str"}) is a 422 validation.failed with errors: {"task": ["must be an object"]}; a missing key is ["is missing"].

RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object

Response samples

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

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (CustomFieldDefinitionGroup)
has_more
required
boolean

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 Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
id
required
string <uuid>

Server-generated UUID v7 identifier.

display_name
string or null

Human-readable name (typically equal to related_name).

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

Canonical module key the definition group applies to. Use this to find the definitions for a given resource type.

parent_id
string or null <uuid>

Self-FK (UUID). Non-null only for SystemOption-scoped sub-groups (Device, DeviceInspection).

Array of objects (CustomFieldDefinition)

Ordered list of field definitions. Order matches position.

created_at
required
string <date-time>

ISO 8601 UTC creation timestamp.

updated_at
required
string <date-time>

ISO 8601 UTC last-mutation timestamp.

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"
}

systems

Serviced equipment on a Site (list + create; System module only). Resolve system_id for jobs, estimates, invoices and sales orders.

List systems

Returns the tenant's Systems — pieces of serviced equipment (HVAC unit, fire panel, etc.) attached to a Site. On tenants with the System module enabled, Job requires a system_id; resolve a valid id here and post it on job create. Discarded rows are filtered out.

Optional filters narrow the list to the parent you're creating a job for: ?site_id=<uuid> (systems on one Site) or ?customer_id=<uuid> (systems on any Site belonging to that Customer).

Available only when the System module is enabled for the tenant (otherwise 403).

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.

site_id
string <uuid>

Filter to systems on a specific Site.

customer_id
string <uuid>

Filter to systems on any Site belonging to this Customer.

object

Filter by custom field value: ?custom_fields[<field_id>]=<value>, where <field_id> is a custom field definition id for this resource (GET /api/v2/custom_field_definitions). Several keys AND together and combine with the other filters, q, sort and the cursor. Equality per field_type: checkbox takes true/false; date takes YYYY-MM-DD; multiselect matches records whose selection contains the value; other types match the exact string. Attachment fields are not filterable. An unknown field id, or a malformed value, returns 400 filter.invalid.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
Array of objects (System)
has_more
required
boolean

Response samples

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

Create a system

Creates a System on a Site. Requires the System module.

Provide site_id (a Site in your tenant) and system_option_id (the type, from GET /api/v2/system_options). system_type and has_device_inventory are derived server-side from the SystemOption, so they are not accepted in the body.

Duplicate guard (web parity): if a System of the same system_option_id already exists on the same site_id, the request is refused with 409 system.duplicate. Resend with "confirmed": true to create it anyway (mirrors the web confirmation dialog).

Authorizations:
bearerAuth
Request Body schema: application/json
required
site_id
required
string <uuid>

UUID v7 FK to the parent Site (must belong to your tenant).

system_option_id
required
string <uuid>

UUID v7 FK to the SystemOption (type). See GET /api/v2/system_options.

description
string

Optional free-text description.

confirmed
boolean

Set true to create even if a system of this type already exists on the site (otherwise 409 system.duplicate).

object

Tenant-defined custom field values keyed by definition UUID. See GET /api/v2/custom_field_definitions?related_name=System.

Responses

Response Headers
RateLimit-Limit
integer

Requests permitted in the current window.

RateLimit-Remaining
integer

Requests remaining in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Response Schema: application/json
required
object (System)

A piece of serviced equipment attached to a Site. Set system_id on a Job (required when the System module is enabled) to one of these ids.

Request samples

Content type
application/json
{
  • "site_id": "72771e6a-6f5e-4de4-a5b9-1266c4197811",
  • "system_option_id": "abbc4268-b361-493d-a39f-efc997227e78",
  • "description": "string",
  • "confirmed": true,
  • "custom_fields": { }
}

Response samples

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