Skip to navigation

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

Recent highlights

  • New: Pinned notes with isPinned on note create and PATCH /notes/{note_id}. Details
  • Updated: POST /users invites with a seat (seatType, now required) and labor cost, and GET /users returns both. Details
  • Updated: Work orders can be labelled with labels on PATCH. Details
  • Updated: Notes lists return replyToId on replies, and PATCH /notes/{note_id} returns the edited note. Details
  • New: Notes on files with GET and POST /files/{file_id}/notes, including @mentions. Details
  • Docs: API keys for Google sign-in users. POST /api-keys needs a password; set one with Forgot password on the login page. SSO sign-in still works. Details
  • Updated: GET /users can include deactivated users with include_inactive=true, and returns isActive. Details
  • Updated: Work orders now return createdBy. Details
  • New: GET /forms lists your company’s forms across all records, with updated_after and other filters. Details
  • Docs: Work-order collaborators on PATCH replaces the full list. Details
  • New: Sub-accounts with parentAccountId on accounts. Details
  • Updated: Project and opportunity lists return the project manager, owner and sales rep, plus phases with include_phases=true. Details
  • New: internalMemo on invoices and bills. Details
  • Updated: Archived work orders can be listed with include_archived=true and fetched by ID. Details
  • Updated: Work order visits now return their description. Details
  • Updated: Viewer seats can no longer create API keys. Details
  • Updated: Calls have custom reasons and dispositions, AI summaries and transcripts, and a company-wide GET /calls. Details
  • New: API key management for superadmins. Details

Looking for a specific date? See the entries below.

Pin notes

New: pin and unpin notes

In Coperniq, a pinned note stays at the top of a record’s notes. You can now pin and unpin notes through the API too.

Pin or unpin an existing note with isPinned on PATCH /notes/{note_id}:

{ "isPinned": true }

Seats and labor cost on user invites

Updated: POST /users takes a seat and a labor cost

Invite User now matches Invite Members in Coperniq. Along with name, email, role and phone, you can set the member’s seat and labor cost.

{
"email": "anna.smith@example.com",
"firstName": "Anna",
"lastName": "Smith",
"seatType": "FIELD",
"roleIds": [3],

Label work orders

Updated: PATCH a work order with labels

You can now set a work order’s labels through the API, the same way you can in Coperniq. This works on the project, opportunity, request, account and client work-order PATCH endpoints, for example PATCH /projects/{project_id}/work-orders/{work_order_id}.

{ "labels": [12, 34] }

Notes on files

New: GET and POST /files/{file_id}/notes

You can now read and add notes (comments) on files, the same way you can on projects and work orders.

  • GET /files/{file_id}/notes lists a file’s notes, newest first, with page and page_size (max 100).
  • POST /files/{file_id}/notes adds a note. Send body, and optionally replyToId to reply to a note on the same file, or fileIds to attach files.

API keys for Google sign-in users

Docs: creating an API key if you sign in with Google

POST /api-keys needs your Coperniq email and password. If you sign in to Coperniq with Google, your account has no password yet, so the request returns 401.

To set one:

  1. On the Coperniq login page, click Forgot password.
  2. Enter the email you sign in with and open the reset link we send you.
  3. Choose a password.

Deactivated users on GET /users

Updated: GET /users can include deactivated users

Records can still point to people who have since been deactivated, as owner, salesRep, projectManager, createdBy, assignee and so on. Add include_inactive=true to GET /users to return deactivated members alongside active ones, so you can resolve those ids to a name and email.

List forms across your company

New: GET /forms

Lists your company’s forms across every project and opportunity, including forms on account work orders, paginated. Use it to sync forms incrementally instead of listing them one record at a time.

Query parameters

ParameterDescription
page, page_sizePagination. page_size defaults to 20, max 100.
updated_after, updated_beforeISO 8601 date or date-time.
project_idForms on this project.
opportunity_idForms on this opportunity.
account_idForms on this account’s work orders. Forms can’t be added to an account directly.
work_order_idForms attached to this work order.
template_idForms created from this template.
statusUNASSIGNED, ASSIGNED, REVIEW, CHANGES_REQUESTED, COMPLETED or CANCELLED.
include_archivedtrue to include archived forms. Default false.
order_byupdated_at (default) or created_at. Ties are broken by id.
orderasc (default) or desc.

Sub-accounts with parentAccountId

New: parentAccountId on accounts

Accounts can now be read and written as sub-accounts of another account.

FieldTypeDescription
parentAccountIdinteger, nullableID of the parent account. null for a top-level account.
  • Read: returned on GET /accounts, GET /accounts/{account_id}, GET /accounts/search, and the POST/PATCH responses.
  • Write: accepted on POST /accounts and PATCH /accounts/{account_id}. Send null on PATCH to unlink an account from its parent.
  • Rules: the parent must be an account in your company and must itself be top-level (1 level of nesting). An account that already has sub-accounts cannot become one, and an account cannot be its own parent. Breaking a rule returns 400 with field: "parentAccountId".

Project and opportunity lists return more of what the single-record GET returns

Updated: GET /projects, GET /projects/search, GET /opportunities, GET /opportunities/search

List and search responses now include the project manager and owner, sales rep and project manager ids, so a bulk sync no longer needs a follow-up GET per record.

  • New fields on every row: ownerId, salesRepId, projectManagerId, and projectManager (null when unset).

Viewer seats can no longer create API keys

Updated: POST /api-keys

Users with a Viewer (view-only) seat in the selected company now get a 403 from POST /api-keys and its legacy alias GET /token, and no key is created. Office and Field seats are unaffected.

  • The seat checked is the one in the company the key is for: the company_id you pass, or your default company. A user who is a Viewer in one company and an Office user in another can still create keys for the second company.
  • Keys that Viewer users created before this change keep working. A superadmin can find them with GET /api-keys and delete them with DELETE /api-keys/{key_id}.

API key management for superadmins, and calls updates

New: List and delete API keys, and archive API users

Superadmins can now manage their company’s API keys through the API. These endpoints authenticate with Basic Auth (your Coperniq email and password), the same as POST /api-keys, not with an API key. Each one takes an optional company_id for logins that belong to more than one company.

Opportunity status on read endpoints, now WON/LOST

Fixed: status is now returned when reading opportunities

GET /opportunities/{opportunity_id}, GET /opportunities, and GET /opportunities/search now include the opportunity’s status. Create and update already accepted it, but these read endpoints never returned it. The legacy /requests aliases return it too.

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.