Welcome to the Coperniq API release notes. This section highlights noteworthy changes across endpoints, schemas, and docs.

Recent highlights

  • New: Account file endpoints — GET/POST /accounts/{account_id}/files, GET/DELETE /accounts/{account_id}/files/{file_id}, and POST /accounts/{account_id}/files/upload, mirroring the existing project/opportunity file endpoints. All file endpoints (project, opportunity/request, account) now validate their path ID: missing or non-numeric returns 400, and an ID that doesn’t match a real record of the expected type returns 404, instead of silently returning an empty list.
  • New: include_line_item_updates on GET /projects and GET /projects/search — opt-in flag (default false) that widens updated_after/updated_before to also match projects whose project-level line items were added or edited in that window, and adds a lineItemsUpdatedAt field to each row (only present when the flag is passed). Line item deletions, reorders, and discount/tax-only edits aren’t detected, and result ordering still follows the project’s own updatedAt.
  • New: Filter GET /opportunities/GET /projects (and their /search variants) by phase entry/exit date via entered_phase_id/exited_phase_id + date bounds — e.g. “opportunities that entered Closed - Won this month” in one query. Filtered rows include a compact phaseTimeline; phaseInstances on the get-by-id endpoints now includes startedAt/completedAt.
  • New: Utilities — read the global utility list via GET /utilities and GET /utilities/{utility_id}, update custom fields via PATCH. Filter by name/holding company/state, ownership_type, or geo lookup via lat+lng, mirroring the existing AHJ endpoints.
  • New: Account/project/opportunity contacts now include isPrimary, title, and description, and are capped at 20 per response. Projects and opportunities now return their own contacts too (independent of the parent account) via include_contacts.
  • Fixed: contact responses now split related records into accounts, projects, opportunities, and vendors (opportunities and vendors were previously dropped); PATCH /contacts/{contact_id} no longer returns stale/empty relations.
  • Fixed: POST/PATCH responses for accounts, projects, and opportunities no longer leak internal-only fields (lineItems, sections, matchedBy, phaseInstanceId, and others); workflowId no longer returns null after creating/updating with an explicit workflow.
  • Updated: trades is now documented as an enum listing every accepted trade type, on both projects and opportunities.

Looking for a specific date? See the entries below.

Archive work orders via the API

Work order update endpoints now accept isActive in the request body. Set isActive: false to archive a work order, or isActive: true to unarchive it — handled independently of any other fields in the same request.

Work order response payloads now also return isActive. The existing isArchived field is deprecated but still returned for backward compatibility — no breaking change.

updatedAt and updated_after/updated_before now reflect custom property changes

updated_after/updated_before on GET /projects (v1 and v2), GET /projects/search, and the other v1 list endpoints (requests/deals, clients/accounts, vendors) now also match records whose custom property values changed within the window. This is always on; there is no new query param.

The updatedAt field on a returned record now reflects the most recent change to that record, including changes to its custom property values.

Account files & project line item date filtering

New: Account file endpoints

Manage files on an account, mirroring the existing project/opportunity file endpoints.

MethodPathDescription
GET/accounts/{account_id}/filesList files on an account
GET/accounts/{account_id}/files/{file_id}Get a file by ID
POST/accounts/{account_id}/filesCreate a file from a URL
POST/accounts/{account_id}/files/uploadCreate a file via multipart upload
DELETE/accounts/{account_id}/files/{file_id}Delete a file

Opportunity & Project Phase Tracking

Improved: Phase instance timestamps on GET /opportunities/{opportunity_id}

Each entry in phaseInstances now includes startedAt and completedAt, matching the projects endpoint. Use these to compute time-in-stage or close dates without a separate lookup.

  • Only populated on GET /opportunities/{opportunity_id}; the list and search endpoints do not return phaseInstances and are unaffected.
  • completedAt is null for the phase the opportunity currently occupies and any that have not been started.

Utilities

New: Utility endpoints

Read and update the global utility list, mirroring the existing AHJ endpoints — utilities are seeded globally with no per-company scoping except for the custom properties.

MethodPathDescription
GET/utilitiesList utilities (pagination, filtering, sorting, or geo lookup by lat+lng)
GET/utilities/{utility_id}Get a utility by ID
PATCH/utilities/{utility_id}Update custom property values on a utility

Account, Project & Opportunity contacts

New: isPrimary and full contact detail

contacts[] on accounts, projects, and opportunities now returns id, name, title, description, emails, phones, and isPrimary for every contact (previously just id/emails/phones on accounts, and nothing at all on projects/opportunities).

  • isPrimary is true for exactly one contact — the one at the lowest position (the first element you send on create/update). At most one contact is ever primary.
  • Projects and opportunities now return contacts too — their own contacts, independent of and not inherited from the parent account’s. Set them on create/update via the contacts request field, same as accounts. Pass include_contacts=true on GET /projects, GET /projects/search, GET /projects/{project_id}, and the equivalent /opportunities endpoints to include it — same as accounts.
  • POST/PATCH responses always include contacts for accounts, projects, and opportunities — not gated behind include_contacts like GET is.

Timesheets

New: Timesheet (time entry) endpoints

Create, read, update, and delete timesheets — time entries logged by a worker against a project, account, or opportunity (and optionally a work order).

MethodPathDescription
GET/timesheetsList timesheets (pagination, filtering, sorting)
POST/timesheetsCreate a timesheet
GET/timesheets/{timesheet_id}Get a timesheet by ID
PATCH/timesheets/{timesheet_id}Update a timesheet, or archive/unarchive it
DELETE/timesheets/{timesheet_id}Delete a timesheet

Bill payments

New: Bill payment endpoints

Record and retrieve payments against a bill, mirroring the existing invoice payment endpoints.

MethodPathDescription
POST/bills/{billId}/paymentsRecord a manual payment against a bill
GET/bills/{billId}/paymentsList all payment records for a bill
GET/bills/{billId}/payments/{paymentId}Get a single payment on a bill by payment id

AHJs and an isCustom flag on properties

New: AHJ (Authorities Having Jurisdiction) endpoints

Look up jurisdictions and manage their custom property values through the public API.

MethodPathDescription
GET/ahjsList AHJs (pagination, name/state code search, filter by state/type/country, or geo lookup via lat+lng)
GET/ahjs/{id}Get an AHJ by ID, including catalog metadata and custom field values
PATCH/ahjs/{id}Update custom property values on an AHJ

Line item sections for projects, invoices, and bills

Line items can now be grouped into named sections — the same grouping you see on quotes and in the Coperniq UI — across project line items, invoices, and bills.

New catalog categories and DELETE behavior change

Envelope product categories

17 new ProductCategory values are now available when creating or updating a catalog item with type=PRODUCT:

ROOF_COVERING, ROOF_UNDERLAYMENT, ROOF_MEMBRANE, ROOF_FLASHING, ROOF_VENT, WALL_CLADDING, WALL_TRIM, WEATHER_BARRIER, WINDOW, EXTERIOR_DOOR, SKYLIGHT, OPENING_FLASHING, GUTTER, DOWNSPOUT, INSULATION, SEALANT, SHEATHING.

Labels — list and create

Manage labels that can be applied to work orders and assets.

Assets — list, create, read, update

Manage physical assets tied to a site and account. Reads use project-service GraphQL; creates and updates call fleet-service.

Quotes — full CRUD

You can now create, read, update, delete, send, and download PDF quotes through the public API.

Project & opportunity title fallback

When a project or opportunity has no title of its own (empty string or whitespace-only), the API now returns the parent account’s title in the title field instead of the blank value.

This applies to all read endpoints that return project or opportunity records:

  • GET /projects, GET /projects/search, GET /projects/{projectId}
  • GET /opportunities, GET /opportunities/search, GET /opportunities/{opportunityId}

Bills

New: Bills endpoints

You can now create, read, update, and delete bills on projects via the public API.

MethodPathDescription
GET/billsList all bills (paginated)
POST/billsCreate a bill
GET/bills/{billId}Get a bill by ID
PATCH/bills/{billId}Update a bill
DELETE/bills/{billId}Delete a bill
GET/projects/{projectId}/billsList bills for a project

Work order notes and work items

New: Work order notes endpoints

You can now create and list notes directly on work orders.

MethodPathDescription
POST/work-orders/{workOrderId}/notesAdd a note to a work order
GET/work-orders/{workOrderId}/notesList all notes on a work order

Create a note

Sites endpoints

Full CRUD support for site records via the public API.

Sites are address/location records that belong to an account. A single account may have multiple sites.

Properties endpoint naming updates

GET /properties — new account and opportunity keys

The /properties response now includes account and opportunity keys in addition to the existing client and request keys:

New keyReplaces
accountclient
opportunityrequest

The record_type query parameter now accepts account and opportunity as values:

Form template update endpoint

Update a form template

A new endpoint lets you update an existing form template:

  • PATCH /form-templates/{formTemplateId} — Update a form template’s name, property layout, assignee, collaborators, labels, and due date configuration.

All fields are optional — only provided fields are updated. formLayouts is optional — when provided, it fully replaces all layout groups and their fields; omit it to leave the existing layout untouched. Include existing groups with their id to preserve them, or omit the id for new groups. Groups not included are removed. Array order determines position on the form.