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

Recent highlights

  • New: Catalog item units, measurements, and categories
    • Catalog items gain unit/unitId, measurements/measurementIds, coverage, and wasteFactorMode/wasteFactorValue — the same quantity-calculation fields available in the Coperniq UI.
    • New GET/GET {id} endpoints for /catalog-units and /measurements, plus a new GET /catalog-categories for discovering valid category/tradeGroup values.
    • measurementIds is replace-all on PATCH, not a merge — pass [] to clear all links.
  • New: Choose the company for a new API key
    • POST /api-keys (and legacy alias GET /token) accept an optional company_id query param, for logins belonging to more than one company.
    • Passing company_id creates the key against that company; you must belong to it, or the request is rejected.
    • Omitting it now falls back deterministically to your first non-collaborator company, then your first company — fixing an inconsistency that could otherwise surface as a Failed to fetch token error.
  • New: Update and delete a payment
    • PATCH/DELETE /payments/{payment_id} — the first payment endpoints scoped by the payment’s own id, not an invoice or bill.
    • PATCH accepts amount, paymentMethod, paymentDate, notes, paymentReference (same fields as create); at least one is required.
    • External (Stripe) payments restrict PATCH to notes only, and reject DELETE entirely — use a refund instead.
    • Updating or deleting a payment recalculates the parent invoice’s/bill’s amountPaid and status automatically.
  • New: Folders
    • GET/POST /projects/{project_id}/folders
    • GET/POST /opportunities/{opportunity_id}/folders
    • GET/PATCH/DELETE /folders/{folder_id}
    • name is required on create, parentId and phaseInstanceId are mutually exclusive, folders nest at most 2 levels deep.
    • Both list endpoints also return workflow phase rows (type: "phase") alongside root-level real folders (type: "folder"), matching the project’s/opportunity’s Docs tab
    • GET /phases/{phase_instance_id}/contents and GET /folders/{folder_id}/contents list a phase’s or folder’s direct children (type: "folder" | "file" | "form" — forms are ordinary files under the hood).
    • Also new: GET/PATCH/DELETE /files/{file_id} — PATCH renames, archives/unarchives, and/or moves a file (via folderId/phaseInstanceId) in one request; GET/DELETE replace the entity-scoped get/delete endpoints, which still work but are no longer documented.
  • New: Create Form PDF
    • POST /forms/{form_id}/pdf to export a form to PDF.

Looking for a specific date? See the entries below.

Catalog item units, measurements, and categories

New: Units, measurements, and quantity-calculation fields on catalog items

Catalog items now expose the same unit and measurement-driven quantity calculation available in the Coperniq UI.

GET/POST/PATCH /catalog-items responses and request bodies gain:

FieldTypeDescription
unit (response) / unitId (request)object / integerThe unit this item is sold or measured in.
measurements (response) / measurementIds (request)arrayMeasurements (or a single sub-measurement) linked to drive this item’s quantity.
coveragenumberMeasurement units covered per item unit (e.g. a bundle covering ~33.33 SF).
wasteFactorMode / wasteFactorValuestring / numberWaste factor applied when computing quantity. Set together, or not at all.

Choose the company for a new API key

POST /api-keys (and its legacy alias GET /token) now accepts an optional company_id query parameter, for accounts where the same login belongs to more than one company. Your company ID can be found in the URL after logging in to the Coperniq UI: https://app.coperniq.io/{company_id}/inbox.

POST /api-keys?company_id=123

File properties and search field fixes

Notes

  • A recognised public field name that doesn’t apply to the record type you’re searching (for example value on an account, or trades on a vendor) now returns 400 naming the field, instead of an empty 200. If you were relying on an empty result for one of these, you’ll now get an explicit error — which is the point: an empty list was indistinguishable from “no matches”.
  • A field name the API does not recognise as a standard field is still treated as a possible custom property, unchanged. An empty result there still means “no records matched” — we can’t tell a typo from a real custom property name without knowing your company’s columns, so only fields we can prove don’t apply are rejected.
  • trades holds a list of values, so a scalar comparison matches if any entry equals your value. Range and substring operators are rejected with a 400 rather than silently matching nothing.

Folders & Folder Contents

Added CRUD for project folders.

  • GET /projects/{project_id}/folders — list root-level folders (not nested in another folder), plus workflow phase rows. Pass include_archived=true to include archived folders (default false).
  • GET /opportunities/{opportunity_id}/folders — same functionality as list project folders.
  • POST /projects/{project_id}/folders - create folder.
  • POST /opportunities/{opportunity_id}/folders - create folder.
  • GET /folders/{folder_id} — get by id.
  • PATCH /folders/{folder_id} — rename, archive/unarchive (isActive), or move (set parentId/phaseInstanceId) — moving isn’t a separate operation.
  • DELETE /folders/{folder_id} — delete (also removes any subfolders and their files).
  • GET /folders/{folder_id}/contents — list a folder’s direct children (subfolders, files, forms).
  • GET /phases/{phase_instance_id}/contents — list a phase’s direct children, same shape as folder contents.

Create Form PDF

You can now use the API to create the form PDF similar to what an automation email would send.

  • POST /forms/{form_id}/pdf to export a form to PDF.
  • Optional body fields: imagesOnly, imagesInRow (0-6), includeImageMetadata.
  • Returns { pdfBase64, contentType, name }.

Technical Details page, and documenting how to archive records

Added a Technical Details page (between Quick Start and Webhooks in the sidebar). Rate limits moved there from the Authentication page — same content, new home. Also added a new Archiving records section: most records can be archived instead of permanently deleted — use that object’s PATCH endpoint and set isActive to false (some resources still use isArchived; check the endpoint reference).

Create a form on a work order

Added POST /work-orders/{work_order_id}/forms to create a form instance directly on a work order, closing the gap where forms could only be created on projects and opportunities.

templateId is required. The form is created with its template’s name — a work order already sits in a phase, so phaseInstanceId is not accepted on this endpoint; the form inherits its placement from the work order’s parent element instance. The response includes workOrderId alongside projectId for correlation, and (when present) phaseInstanceId/phaseTemplateId.

Create forms on opportunities

Added POST /opportunities/{opportunity_id}/forms to create a form instance directly on an opportunity, mirroring the existing POST /projects/{project_id}/forms. templateId is required, name is optional (defaults to the template’s name), and opportunities cannot be assigned to a phase, so phaseInstanceId is not accepted or returned on this endpoint.

The response from both create-form endpoints now also returns templateId, name, createdAt, and updatedAt directly on the top-level object.

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.