> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.coperniq.io/api-reference/catalog-items/update-catalog-item/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coperniq.io/_mcp/server. # Update Catalog Item PATCH https://api.coperniq.io/v1/catalog-items/{catalog_item_id} Content-Type: application/json Update an existing catalog item. Reference: https://docs.coperniq.io/api-reference/catalog-items/update-catalog-item ## Authentication - `x-api-key` header (required) — API Key authentication via header ## Request ### Path parameters - `catalog_item_id` (integer, required) — Catalog item identifier ### Body (application/json) This endpoint expects a CatalogItemUpdate. - `type` (enum, optional) — Catalog item type - Allowed values: `PRODUCT`, `SERVICE` - `name` (string, optional) — Catalog item name - `category` (CatalogCategoryCode, optional) — Catalog item category code. Must be a valid ProductCategory or ServiceCategory value. - `code` (string, optional) — Catalog item code - `description` (string, optional, nullable) — Catalog item description - `cost` (double, optional) — Catalog item cost - `price` (double, optional) — Catalog item price - `tradeGroup` (enum, optional) — High-level trade group for this item. - Allowed values: `ENERGY`, `MECHANICAL`, `ELECTRICAL`, `PLUMBING`, `LOW_VOLTAGE`, `ENVELOPE`, `OTHER` - `manufacturer` (string, optional, nullable) — Catalog item manufacturer. PRODUCT items only. - `sku` (string, optional, nullable) — Catalog item SKU. PRODUCT items only — sending an SKU while the item is, or is becoming, a SERVICE returns 400. Converting a PRODUCT that has an SKU into a SERVICE is allowed and does not require clearing it first: services have no SKU, so it is dropped as part of the conversion. Pass null to clear a PRODUCT SKU or during that conversion; an already-SERVICE item with a legacy SKU cannot change or clear it through this API and returns 400. - `isArchived` (boolean, optional) — Set true to archive the item, or false to restore an archived one. Archiving via this field is equivalent to DELETE /catalog-items/\{catalog\_item\_id}. - `preferredVendorId` (integer, optional, nullable) — ID of the preferred vendor. Pass null to clear the preferred vendor. - `unitId` (integer, optional, nullable) — ID of the unit this item is sold/measured in. Pass null to clear it. - `measurementIds` (list of integer, optional) — Replaces the full set of measurement/sub-calculation links driving this item's quantity. Omit this field to leave existing links unchanged; pass an empty array to clear all links. This is NOT a merge/append — any previously linked measurement not included here is unlinked. At most one linked measurement may be a sub-measurement (type SUB_CALCULATION), and it must be the only one linked. - `coverage` (double, optional, nullable) — Measurement units covered per item unit. Must be greater than zero. - `wasteFactorMode` (enum, optional, nullable) — Waste factor mode. Must be set together with wasteFactorValue — both, or neither. - Allowed values: `STATIC` - `wasteFactorValue` (double, optional, nullable) — Waste factor percentage (0-100). Must be set together with wasteFactorMode. ## Response ### 200 Catalog item updated successfully - `id` (integer, required) — Catalog item identifier - `name` (string, required) — Catalog item name - `catalog` (enum, required, nullable) — High-level trade group for this item. - Allowed values: `ENERGY`, `MECHANICAL`, `ELECTRICAL`, `PLUMBING`, `LOW_VOLTAGE`, `ENVELOPE`, `OTHER` - `type` (enum, required) — Catalog item type - Allowed values: `PRODUCT`, `SERVICE` - `category` (CatalogCategoryCode, required) — Catalog item category code (ProductCategory or ServiceCategory) - `cost` (double, required, nullable) — Catalog item cost - `price` (double, required, nullable) — Catalog item price - `manufacturer` (string, optional, nullable) — Catalog item manufacturer - `sku` (string, optional, nullable) — Catalog item SKU - `code` (string, optional, nullable) — Catalog item code - `description` (string, optional, nullable) — Catalog item description - `image` (CatalogItemImage, optional, nullable) - `isArchived` (boolean, optional, nullable) — Whether the catalog item is archived - `createdById` (integer, optional) — Identifier of the user who created the catalog item - `createdAt` (string, optional, nullable) - `updatedAt` (string, optional, nullable) - `preferredVendorId` (integer, optional, nullable) — ID of the preferred vendor (must be an existing active vendor for this company) - `unit` (CatalogUnit, optional, nullable) — The unit this item is sold/measured in, if any. Set on write via `unitId`. - `measurements` (list of Measurement, optional) — Measurements (and sub-measurements) linked to this item to drive its quantity calculation. A sub-measurement is a linked Measurement with `type SUB_CALCULATION`; at most one may be linked, and it must be the only link when present. - `coverage` (double, optional, nullable) — Measurement units covered per item unit (e.g. a bundle covering ~33.33 SF). Used to size quantity from summed linked measurements; not used when the item is linked to a sub-calculation, which derives quantity from its own formula. - `wasteFactorMode` (enum, optional, nullable) — Waste factor mode applied when computing this item's quantity. Currently only STATIC is supported. Paired with wasteFactorValue — both are set, or neither. - Allowed values: `STATIC` - `wasteFactorValue` (double, optional, nullable) — Waste factor percentage (0-100) applied when computing this item's quantity. Paired with wasteFactorMode. ## Errors ### 400 Bad Request Error Invalid request - `message` (string, optional) - `code` (enum, optional) - Allowed values: `UPSTREAM_ERROR` - `field` (string, optional) — Field that caused the validation error (if applicable) ### 401 Unauthorized Error Authentication failed - `message` (string, optional) - `code` (enum, optional) - Allowed values: `UPSTREAM_ERROR` ### 404 Not Found Error Resource not found - `message` (string, optional) - `code` (enum, optional) - Allowed values: `UPSTREAM_ERROR` ### 502 Bad Gateway Error The catalog item was updated, but could not be read back afterwards, so the full response (including any `unit` and `measurements` links) is unavailable. The update itself did apply. If this request did not archive the item, fetch the current state with `GET /catalog-items/{catalog_item_id}`. If it set `isArchived: true`, that endpoint (and the list endpoint) excludes archived catalog items by design, so it will return 404 even though the archive succeeded — treat that 404 as confirmation, not a failure, and do not retry the PATCH. - `code` (enum, optional) - Allowed values: `UPSTREAM_ERROR` - `message` (string, optional) - `id` (integer, optional) — The id of the catalog item that was updated. ## Types ### CatalogCategoryCode Catalog item category code. Must be: - one of `ProductCategory` when `type` is `PRODUCT`, or - one of `ServiceCategory` when `type` is `SERVICE`. ### CatalogItemImage - `id` (integer, optional) - `name` (string, optional) - `url` (string, optional) — URL to download the image ### CatalogUnit A unit of measure from the company's unit registry (e.g. Each, Square Feet, Linear Feet). Referenced by catalog items and measurements via `unitId`. - `id` (integer, required) — Catalog unit identifier - `code` (string, required) — Stable short code for the unit (e.g. "SF", "EA") - `label` (string, required) — Human-readable label for the unit (e.g. "Square Feet") - `dimension` (enum, optional, nullable) — The physical dimension this unit measures, if any. Null for packaging-style units (e.g. bundle, roll) that can't be used on a measurement. - Allowed values: `LENGTH`, `AREA`, `COUNT` - `isCustom` (boolean, optional) — Whether this unit was defined by the company, as opposed to one of the seeded defaults. - `isArchived` (boolean, optional) — Whether this unit has been archived. ### Measurement A named measurement or sub-calculation that a catalog item can link to (via `measurementIds`) to drive its quantity. A "sub-measurement" is simply a Measurement with `type: SUB_CALCULATION` — not a separate structure. - `id` (integer, required) — Measurement identifier - `name` (string, required) — Measurement name - `type` (enum, required) — A plain MEASUREMENT is a raw input value; a SUB_CALCULATION ("sub-measurement") derives its value from a formula over other measurements. - Allowed values: `MEASUREMENT`, `SUB_CALCULATION` - `unit` (CatalogUnit, optional, nullable) — The unit this measurement is expressed in, if any. - `minimumValue` (double, optional, nullable) — Minimum allowed value. Only meaningful for type MEASUREMENT. - `formula` (MeasurementFormula, optional) — The formula driving a SUB\_CALCULATION measurement's quantity. Read-only — there is no API to create or edit a measurement's formula; it can only be authored in the app. Shape: `operands` names each referenced measurement (`{name, measurementId}`); `ast` is a binary-operation tree (`ADD`/`SUB`/`MUL`/`DIV`) whose leaves are either `{operandRef}` (referencing an entry in `operands` by name) or `{constant}` (a literal number); `sourceText` is the original formula text as authored. - `isArchived` (boolean, optional) — Whether this measurement has been archived. ### MeasurementFormula The formula driving a SUB\_CALCULATION measurement's quantity. Read-only — there is no API to create or edit a measurement's formula; it can only be authored in the app. Shape: `operands` names each referenced measurement (`{name, measurementId}`); `ast` is a binary-operation tree (`ADD`/`SUB`/`MUL`/`DIV`) whose leaves are either `{operandRef}` (referencing an entry in `operands` by name) or `{constant}` (a literal number); `sourceText` is the original formula text as authored. - `operands` (list of MeasurementFormulaOperandsItems, optional) - `ast` (MeasurementFormulaAst, optional) — Binary-operation AST node or leaf. See the formula description above. - `sourceText` (string, optional) — The formula as originally authored (e.g. "( m1 + m2 ) / 100") ### MeasurementFormulaOperandsItems - `name` (string, optional) - `measurementId` (integer, optional) ### MeasurementFormulaAst Binary-operation AST node or leaf. See the formula description above. ## Examples ### Update a SERVICE **Request** ```json { "type": "SERVICE", "price": 550, "isArchived": true } ``` **Response** ```json { "id": 1, "name": "string", "catalog": "ENERGY", "type": "PRODUCT", "category": "BATTERY_SYSTEM", "cost": 1.1, "price": 1.1, "manufacturer": "string", "sku": "string", "code": "string", "description": "string", "image": { "id": 1, "name": "string", "url": "string" }, "isArchived": true, "createdById": 1, "createdAt": "2024-01-15T09:30:00Z", "updatedAt": "2024-01-15T09:30:00Z", "preferredVendorId": 1, "unit": { "id": 1, "code": "string", "label": "string", "dimension": "LENGTH", "isCustom": true, "isArchived": true }, "measurements": [ { "id": 1, "name": "string", "type": "MEASUREMENT", "unit": { "id": 1, "code": "string", "label": "string", "dimension": "LENGTH", "isCustom": true, "isArchived": true }, "minimumValue": 1.1, "formula": { "operands": [ { "name": "string", "measurementId": 1 } ], "ast": {}, "sourceText": "string" }, "isArchived": true } ], "coverage": 1.1, "wasteFactorMode": "STATIC", "wasteFactorValue": 1.1 } ``` **SDK Code** ```python Update a SERVICE import requests url = "https://api.coperniq.io/v1/catalog-items/1" payload = { "type": "SERVICE", "price": 550, "isArchived": True } headers = { "x-api-key": "", "Content-Type": "application/json" } response = requests.patch(url, json=payload, headers=headers) print(response.json()) ``` ```javascript Update a SERVICE const url = 'https://api.coperniq.io/v1/catalog-items/1'; const options = { method: 'PATCH', headers: {'x-api-key': '', 'Content-Type': 'application/json'}, body: '{"type":"SERVICE","price":550,"isArchived":true}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go Update a SERVICE package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.coperniq.io/v1/catalog-items/1" payload := strings.NewReader("{\n \"type\": \"SERVICE\",\n \"price\": 550,\n \"isArchived\": true\n}") req, _ := http.NewRequest("PATCH", url, payload) req.Header.Add("x-api-key", "") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby Update a SERVICE require 'uri' require 'net/http' url = URI("https://api.coperniq.io/v1/catalog-items/1") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Patch.new(url) request["x-api-key"] = '' request["Content-Type"] = 'application/json' request.body = "{\n \"type\": \"SERVICE\",\n \"price\": 550,\n \"isArchived\": true\n}" response = http.request(request) puts response.read_body ``` ```java Update a SERVICE import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.patch("https://api.coperniq.io/v1/catalog-items/1") .header("x-api-key", "") .header("Content-Type", "application/json") .body("{\n \"type\": \"SERVICE\",\n \"price\": 550,\n \"isArchived\": true\n}") .asString(); ``` ```php Update a SERVICE request('PATCH', 'https://api.coperniq.io/v1/catalog-items/1', [ 'body' => '{ "type": "SERVICE", "price": 550, "isArchived": true }', 'headers' => [ 'Content-Type' => 'application/json', 'x-api-key' => '', ], ]); echo $response->getBody(); ``` ```csharp Update a SERVICE using RestSharp; var client = new RestClient("https://api.coperniq.io/v1/catalog-items/1"); var request = new RestRequest(Method.PATCH); request.AddHeader("x-api-key", ""); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"type\": \"SERVICE\",\n \"price\": 550,\n \"isArchived\": true\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift Update a SERVICE import Foundation let headers = [ "x-api-key": "", "Content-Type": "application/json" ] let parameters = [ "type": "SERVICE", "price": 550, "isArchived": true ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.coperniq.io/v1/catalog-items/1")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "PATCH" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` ### Example 2 **Request** ```json { "type": "PRODUCT", "name": "New Panel Name", "manufacturer": "Acme Solar" } ``` **Response** ```json { "id": 1, "name": "string", "catalog": "ENERGY", "type": "PRODUCT", "category": "BATTERY_SYSTEM", "cost": 1.1, "price": 1.1, "manufacturer": "string", "sku": "string", "code": "string", "description": "string", "image": { "id": 1, "name": "string", "url": "string" }, "isArchived": true, "createdById": 1, "createdAt": "2024-01-15T09:30:00Z", "updatedAt": "2024-01-15T09:30:00Z", "preferredVendorId": 1, "unit": { "id": 1, "code": "string", "label": "string", "dimension": "LENGTH", "isCustom": true, "isArchived": true }, "measurements": [ { "id": 1, "name": "string", "type": "MEASUREMENT", "unit": { "id": 1, "code": "string", "label": "string", "dimension": "LENGTH", "isCustom": true, "isArchived": true }, "minimumValue": 1.1, "formula": { "operands": [ { "name": "string", "measurementId": 1 } ], "ast": {}, "sourceText": "string" }, "isArchived": true } ], "coverage": 1.1, "wasteFactorMode": "STATIC", "wasteFactorValue": 1.1 } ``` **SDK Code** ```python import requests url = "https://api.coperniq.io/v1/catalog-items/1" payload = { "type": "PRODUCT", "name": "New Panel Name", "manufacturer": "Acme Solar" } headers = { "x-api-key": "", "Content-Type": "application/json" } response = requests.patch(url, json=payload, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.coperniq.io/v1/catalog-items/1'; const options = { method: 'PATCH', headers: {'x-api-key': '', 'Content-Type': 'application/json'}, body: '{"type":"PRODUCT","name":"New Panel Name","manufacturer":"Acme Solar"}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.coperniq.io/v1/catalog-items/1" payload := strings.NewReader("{\n \"type\": \"PRODUCT\",\n \"name\": \"New Panel Name\",\n \"manufacturer\": \"Acme Solar\"\n}") req, _ := http.NewRequest("PATCH", url, payload) req.Header.Add("x-api-key", "") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.coperniq.io/v1/catalog-items/1") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Patch.new(url) request["x-api-key"] = '' request["Content-Type"] = 'application/json' request.body = "{\n \"type\": \"PRODUCT\",\n \"name\": \"New Panel Name\",\n \"manufacturer\": \"Acme Solar\"\n}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.patch("https://api.coperniq.io/v1/catalog-items/1") .header("x-api-key", "") .header("Content-Type", "application/json") .body("{\n \"type\": \"PRODUCT\",\n \"name\": \"New Panel Name\",\n \"manufacturer\": \"Acme Solar\"\n}") .asString(); ``` ```php request('PATCH', 'https://api.coperniq.io/v1/catalog-items/1', [ 'body' => '{ "type": "PRODUCT", "name": "New Panel Name", "manufacturer": "Acme Solar" }', 'headers' => [ 'Content-Type' => 'application/json', 'x-api-key' => '', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.coperniq.io/v1/catalog-items/1"); var request = new RestRequest(Method.PATCH); request.AddHeader("x-api-key", ""); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"type\": \"PRODUCT\",\n \"name\": \"New Panel Name\",\n \"manufacturer\": \"Acme Solar\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "x-api-key": "", "Content-Type": "application/json" ] let parameters = [ "type": "PRODUCT", "name": "New Panel Name", "manufacturer": "Acme Solar" ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.coperniq.io/v1/catalog-items/1")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "PATCH" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` > Official Coperniq API documentation. Build integrations for solar and construction project management — projects, opportunities, work orders, invoices, webhooks, and more.