> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coperniq.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coperniq.io/_mcp/server.

# Update Project Work Order

PATCH https://api.coperniq.io/v1/projects/{project_id}/work-orders/{work_order_id}
Content-Type: application/json

Update a project's work order. Supports partial updates.

Updatable fields:
- title, description, priority
- startDate, endDate, startDateAllDay, endDateAllDay
- status (label string)
- assigneeId
- visits: Only applicable on field work orders. When provided, the `visits` array REPLACES the full set of visits for the work order. Include all existing visits you wish to keep or update (with their `id`), and include new visits without an `id`. Any existing visit not included in the request will be removed. Visit booleans default to false when omitted.
- checklist: Update the checklist for the work order.
    When this field is provided, the `checklist` array REPLACES the full set of checklist items on the work order.
    - Include every existing item you want to keep or modify (identified by `id`). 
    - Any existing item NOT included in this array will be removed.
    - Each item MUST include an `id`. Items without an `id` are ignored (checklist item creation is not supported).
- isActive: Set to `false` to archive the work order, or `true` to unarchive it. Applied independently of any other fields present in the same request.
- labels: Array of label IDs that REPLACES the work order's full label set. Order is kept, and the first label is the primary (display) label. Send `[]` to remove all labels; omit to leave them unchanged. Maximum 20 unique IDs, each an existing `WORK`-type label in your company (see `GET /labels?type=WORK`). Invalid, unknown or non-`WORK` IDs return `400` and nothing is updated.


Reference: https://docs.coperniq.io/api-reference/work-orders/update-project-work-order

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Request

### Path parameters

- `project_id` (integer, required) — Project identifier
- `work_order_id` (integer, required) — Work order identifier

### Body (application/json)

This endpoint expects a WorkOrderUpdate.

- `title` (string, optional)
- `description` (string, optional)
- `priority` (integer, optional)
- `startDate` (string, optional)
- `startDateAllDay` (boolean, optional)
- `endDate` (string, optional)
- `endDateAllDay` (boolean, optional)
- `status` (string, optional) — Status label
- `assigneeId` (integer, optional) — User identifier to assign to the work order
- `collaborators` (list of integer, optional) — User IDs to set as the work order's collaborators. REPLACES the full collaborator list; include everyone who should remain. Send `[]` to remove all collaborators. Omit to leave unchanged.
- `visits` (list of VisitUpsert, optional) — Only applicable on field work orders. When provided, the `visits` array REPLACES the full set of visits for the work order. Include all existing visits you wish to keep or update (with their `id`), and include new visits without an `id`. Any existing visit not included in the request will be removed. Visit booleans default to false when omitted.
- `checklist` (list of ChecklistItemUpdate, optional) — Update the checklist for the work order. When this field is provided, the `checklist` array REPLACES the full set of checklist items on the work order. - Include every existing item you want to keep or modify (identified by `id`). - Any existing item NOT included in this array will be removed. - Each item MUST include an `id`. Items without an `id` are ignored (checklist item creation is not supported).
- `isActive` (boolean, optional, nullable) — Set to `false` to archive the work order, or `true` to unarchive it. Applied independently of any other fields present in the same request.
- `labels` (list of integer, optional) — Label IDs to set as the work order's labels. REPLACES the full label set, so include every label that should remain. The order is kept, and the first label is the primary (display) label. Send `[]` to remove all labels. Omit to leave labels unchanged. Every ID must be an existing `WORK`-type label in your company (see `GET /labels?type=WORK`). Otherwise the request returns `400` listing the invalid IDs, and nothing is updated.

## Response

### 200

Work order updated successfully

- `id` (integer, optional) — Work order identifier
- `uid` (integer, optional) — Unique work order number within the project
- `templateId` (integer, optional) — Template identifier for the work order
- `createdAt` (string, optional) — Creation timestamp
- `updatedAt` (string, optional) — Last update timestamp
- `title` (string, optional) — Work order title
- `description` (string, optional) — Work order description
- `priority` (integer, optional) — Priority level
- `startDate` (string, optional, nullable) — Scheduled start date
- `startDateAllDay` (boolean, optional) — Whether the start date is an all-day event
- `endDate` (string, optional, nullable) — Scheduled end date
- `endDateAllDay` (boolean, optional) — Whether the end date is an all-day event
- `completedAt` (string, optional, nullable) — Timestamp when the work order was completed
- `isActive` (boolean, optional) — Whether the work order is active (not archived)
- `isCompleted` (boolean, optional) — Whether the work order is completed
- `isField` (boolean, optional) — Whether the work order is a field task
- `labels` (list of WorkOrderLabelsItems, optional) — Labels associated with the work order
- `project` (WorkOrderProject, optional) — Project associated with the work order
- `createdBy` (WorkOrderCreatedBy, optional, nullable) — User who created the work order. `null` when unknown. Create responses return `{ id }` only.
- `assignee` (WorkOrderAssignee, optional, nullable) — User assigned to the work order
- `collaborators` (list of UserSummary, optional) — Additional users collaborating on the work order
- `visits` (WorkOrderVisits, optional) — Site visits associated with the work order (legacy shape)
- `statuses` (list of WorkOrderStatusesItems, optional) — History of status changes and time spent in each status
- `status` (WorkOrderStatus, optional, nullable) — Current status of the work order
- `checklist` (list of ChecklistItem, optional) — Checklist items for the work order
- `workItems` (list of WorkOrderWorkItem, optional) — Work items linked to this work order via Blueprint workflow. Includes forms (`FORM`) and file requests (`FILE_REQUEST`). Returns [] when none are linked.
- `lineItems` (list of WorkOrderLineItem, optional) — Present only for service work orders on an **account** (parent project type `ACCOUNT`), and only when at least one line item exists. Amounts are in dollars with cent precision. Replace via `PUT /work-orders/{work_order_id}/line-items`. Omitted from the response when not applicable or when there are no line items.
- `isArchived` (boolean, optional, deprecated) — Deprecated: use `isActive` instead. Kept for backward compatibility.

## Errors

### 400 Bad Request Error

Invalid request, for example a `labels` id that is not an existing `WORK`-type label in your company

- `message` (string, optional)
- `code` (enum, optional)
  - Allowed values: `NOT_FOUND`
- `field` (string, optional) — Field that caused the validation error (if applicable)

### 401 Unauthorized Error

Authentication failed

- `message` (string, optional)
- `code` (enum, optional)
  - Allowed values: `NOT_FOUND`

### 404 Not Found Error

Resource not found

- `message` (string, optional)
- `code` (enum, optional)
  - Allowed values: `NOT_FOUND`

## Types

### VisitUpsert

Shape used to create or update visits in work order PATCH requests.

- `id` (integer, optional) — Include to update an existing visit; omit to create new. When `visits` is sent, any existing visit not included will be deleted.
- `description` (string, optional)
- `startDate` (string, optional)
- `endDate` (string, optional)
- `startDateAllDay` (boolean, optional, default: false)
- `endDateAllDay` (boolean, optional, default: false)
- `isCompleted` (boolean, optional)

### ChecklistItemUpdate

Checklist item update payload. Only fields provided are updated.

- `id` (integer, required) — Checklist item identifier to update (required).
- `detail` (string, optional) — New detail text.
- `isCompleted` (boolean, optional) — Completion flag.

### WorkOrderLabelsItems

- `label` (WorkOrderLabelsItemsLabel, optional)

### WorkOrderProject

Project associated with the work order

- `id` (integer, optional) — Project identifier
- `uid` (integer, optional) — Project number
- `title` (string, optional) — Project title
- `address` (list of string, optional) — Project address
- `stage` (string, optional, nullable) — Project stage (if available)

### WorkOrderCreatedBy

User who created the work order. `null` when unknown. Create responses return `{ id }` only.

- `id` (integer, optional) — User identifier
- `firstName` (string, optional, nullable) — User's first name
- `lastName` (string, optional, nullable) — User's last name

### WorkOrderAssignee

User assigned to the work order

- `id` (integer, optional) — User identifier
- `firstName` (string, optional) — User's first name
- `lastName` (string, optional) — User's last name
- `email` (string, optional) — User's email address

### UserSummary

Minimal user representation used in responses

- `id` (integer, optional)
- `firstName` (string, optional, nullable)
- `lastName` (string, optional, nullable)
- `email` (string, optional, nullable)
- `avatarUrl` (string, optional, nullable)

### WorkOrderVisits

Site visits associated with the work order (legacy shape)

- `visits` (list of Visit, optional) — List of visits

### WorkOrderStatusesItems

- `count` (string, optional) — Number of times this status was entered
- `endedAt` (string, optional) — When the status ended
- `spentTime` (double, optional) — Time spent in this status in seconds
- `startedAt` (string, optional) — When the status started
- `status` (string, optional) — Status identifier

### WorkOrderStatus

Current status of the work order

- `id` (string, optional, nullable) — Status identifier
- `label` (string, optional, nullable) — Human readable status label

### ChecklistItem

- `id` (integer, optional) — Checklist item identifier
- `detail` (string, optional) — Checklist task detail
- `isCompleted` (boolean, optional) — Whether the checklist item is completed
- `completedAt` (string, optional, nullable) — Completion timestamp, if completed

### WorkOrderWorkItem

- `type`: `FORM` (FORM)
  - `id` (integer, required) — Form instance ID
  - `completedAt` (string, optional, nullable)
  - `dueDate` (string, optional, nullable) — Due date ISO string
  - `isCompleted` (boolean, optional)
  - `name` (string, optional, nullable) — Form name
  - `status` (string, optional, nullable) — Form status (e.g. OPEN, IN_PROGRESS, COMPLETED)
  - `templateId` (integer, optional, nullable) — Template the form was created from
- `type`: `FILE_REQUEST` (FILE_REQUEST)
  - `id` (integer, required) — Action instance ID
  - `name` (string, optional, nullable) — File request title
  - `status` (string, optional, nullable) — Element instance status (e.g. OPEN, IN_PROGRESS, COMPLETED)

### WorkOrderLineItem

- `id` (integer, optional)
- `quantity` (integer, optional)
- `description` (string, optional, nullable)
- `unitCost` (double, optional)
- `totalCost` (double, optional, nullable)
- `unitPrice` (double, optional)
- `totalPrice` (double, optional, nullable)
- `sectionName` (string, optional, nullable)
- `catalogItem` (WorkOrderLineItemCatalogItem, optional, nullable)

### WorkOrderLabelsItemsLabel

- `id` (integer, optional) — Label identifier
- `label` (string, optional) — Label name
- `color` (string, optional) — Label color (hex code)

### Visit

Only applicable on field work orders.

- `id` (integer, optional) — Visit identifier
- `description` (string, optional, nullable) — Free-text note on the visit, as written by `PATCH /projects/{project_id}/work-orders/{work_order_id}`. `null` when none was set.
- `startDate` (string, optional, nullable)
- `endDate` (string, optional, nullable)
- `startDateAllDay` (boolean, optional, default: false)
- `endDateAllDay` (boolean, optional, default: false)
- `isCompleted` (boolean, optional)

### WorkOrderLineItemCatalogItem

- `id` (integer, optional)
- `name` (string, optional)
- `sku` (string, optional, nullable)
- `type` (string, optional, nullable)
- `manufacturer` (string, optional, nullable)

## Examples

### Remove all labels

**Request**

```json
{
  "labels": []
}
```

**Response**

```json
{
  "id": 1,
  "uid": 1,
  "templateId": 1,
  "createdAt": "2024-01-15T09:30:00Z",
  "updatedAt": "2024-01-15T09:30:00Z",
  "title": "string",
  "description": "string",
  "priority": 1,
  "startDate": "2024-01-15T09:30:00Z",
  "startDateAllDay": true,
  "endDate": "2024-01-15T09:30:00Z",
  "endDateAllDay": true,
  "completedAt": "2024-01-15T09:30:00Z",
  "isActive": true,
  "isCompleted": true,
  "isField": true,
  "labels": [
    {
      "label": {
        "id": 1,
        "label": "string",
        "color": "string"
      }
    }
  ],
  "project": {
    "id": 1,
    "uid": 1,
    "title": "string",
    "address": [
      "string"
    ],
    "stage": "string"
  },
  "createdBy": {
    "id": 1,
    "firstName": "string",
    "lastName": "string"
  },
  "assignee": {
    "id": 1,
    "firstName": "string",
    "lastName": "string",
    "email": "string"
  },
  "collaborators": [
    {
      "id": 1,
      "firstName": "string",
      "lastName": "string",
      "email": "string",
      "avatarUrl": "string"
    }
  ],
  "visits": {
    "visits": [
      {
        "id": 1,
        "description": "string",
        "startDate": "2024-01-15T09:30:00Z",
        "endDate": "2024-01-15T09:30:00Z",
        "startDateAllDay": false,
        "endDateAllDay": false,
        "isCompleted": true
      }
    ]
  },
  "statuses": [
    {
      "count": "string",
      "endedAt": "2024-01-15T09:30:00Z",
      "spentTime": 1.1,
      "startedAt": "2024-01-15T09:30:00Z",
      "status": "string"
    }
  ],
  "status": {
    "id": "string",
    "label": "string"
  },
  "checklist": [
    {
      "id": 1,
      "detail": "string",
      "isCompleted": true,
      "completedAt": "2024-01-15T09:30:00Z"
    }
  ],
  "workItems": [
    {
      "type": "FILE_REQUEST",
      "id": 2556,
      "name": "Shade Report",
      "status": "IN_PROGRESS"
    }
  ],
  "lineItems": [
    {
      "id": 1,
      "quantity": 1,
      "description": "string",
      "unitCost": 1.1,
      "totalCost": 1.1,
      "unitPrice": 1.1,
      "totalPrice": 1.1,
      "sectionName": "string",
      "catalogItem": {
        "id": 1,
        "name": "string",
        "sku": "string",
        "type": "string",
        "manufacturer": "string"
      }
    }
  ],
  "isArchived": true
}
```

**SDK Code**

```python Remove all labels
import requests

url = "https://api.coperniq.io/v1/projects/1/work-orders/1"

payload = { "labels": [] }
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Remove all labels
const url = 'https://api.coperniq.io/v1/projects/1/work-orders/1';
const options = {
  method: 'PATCH',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"labels":[]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Remove all labels
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.coperniq.io/v1/projects/1/work-orders/1"

	payload := strings.NewReader("{\n  \"labels\": []\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	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 Remove all labels
require 'uri'
require 'net/http'

url = URI("https://api.coperniq.io/v1/projects/1/work-orders/1")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"labels\": []\n}"

response = http.request(request)
puts response.read_body
```

```java Remove all labels
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.coperniq.io/v1/projects/1/work-orders/1")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"labels\": []\n}")
  .asString();
```

```php Remove all labels
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.coperniq.io/v1/projects/1/work-orders/1', [
  'body' => '{
  "labels": []
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp Remove all labels
using RestSharp;

var client = new RestClient("https://api.coperniq.io/v1/projects/1/work-orders/1");
var request = new RestRequest(Method.PATCH);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"labels\": []\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Remove all labels
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["labels": []] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.coperniq.io/v1/projects/1/work-orders/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
{
  "labels": [
    12,
    34
  ]
}
```

**Response**

```json
{
  "id": 1,
  "uid": 1,
  "templateId": 1,
  "createdAt": "2024-01-15T09:30:00Z",
  "updatedAt": "2024-01-15T09:30:00Z",
  "title": "string",
  "description": "string",
  "priority": 1,
  "startDate": "2024-01-15T09:30:00Z",
  "startDateAllDay": true,
  "endDate": "2024-01-15T09:30:00Z",
  "endDateAllDay": true,
  "completedAt": "2024-01-15T09:30:00Z",
  "isActive": true,
  "isCompleted": true,
  "isField": true,
  "labels": [
    {
      "label": {
        "id": 1,
        "label": "string",
        "color": "string"
      }
    }
  ],
  "project": {
    "id": 1,
    "uid": 1,
    "title": "string",
    "address": [
      "string"
    ],
    "stage": "string"
  },
  "createdBy": {
    "id": 1,
    "firstName": "string",
    "lastName": "string"
  },
  "assignee": {
    "id": 1,
    "firstName": "string",
    "lastName": "string",
    "email": "string"
  },
  "collaborators": [
    {
      "id": 1,
      "firstName": "string",
      "lastName": "string",
      "email": "string",
      "avatarUrl": "string"
    }
  ],
  "visits": {
    "visits": [
      {
        "id": 1,
        "description": "string",
        "startDate": "2024-01-15T09:30:00Z",
        "endDate": "2024-01-15T09:30:00Z",
        "startDateAllDay": false,
        "endDateAllDay": false,
        "isCompleted": true
      }
    ]
  },
  "statuses": [
    {
      "count": "string",
      "endedAt": "2024-01-15T09:30:00Z",
      "spentTime": 1.1,
      "startedAt": "2024-01-15T09:30:00Z",
      "status": "string"
    }
  ],
  "status": {
    "id": "string",
    "label": "string"
  },
  "checklist": [
    {
      "id": 1,
      "detail": "string",
      "isCompleted": true,
      "completedAt": "2024-01-15T09:30:00Z"
    }
  ],
  "workItems": [
    {
      "type": "FILE_REQUEST",
      "id": 2556,
      "name": "Shade Report",
      "status": "IN_PROGRESS"
    }
  ],
  "lineItems": [
    {
      "id": 1,
      "quantity": 1,
      "description": "string",
      "unitCost": 1.1,
      "totalCost": 1.1,
      "unitPrice": 1.1,
      "totalPrice": 1.1,
      "sectionName": "string",
      "catalogItem": {
        "id": 1,
        "name": "string",
        "sku": "string",
        "type": "string",
        "manufacturer": "string"
      }
    }
  ],
  "isArchived": true
}
```

**SDK Code**

```python
import requests

url = "https://api.coperniq.io/v1/projects/1/work-orders/1"

payload = { "labels": [12, 34] }
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.coperniq.io/v1/projects/1/work-orders/1';
const options = {
  method: 'PATCH',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"labels":[12,34]}'
};

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/projects/1/work-orders/1"

	payload := strings.NewReader("{\n  \"labels\": [\n    12,\n    34\n  ]\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	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/projects/1/work-orders/1")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"labels\": [\n    12,\n    34\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.coperniq.io/v1/projects/1/work-orders/1")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"labels\": [\n    12,\n    34\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.coperniq.io/v1/projects/1/work-orders/1', [
  'body' => '{
  "labels": [
    12,
    34
  ]
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.coperniq.io/v1/projects/1/work-orders/1");
var request = new RestRequest(Method.PATCH);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"labels\": [\n    12,\n    34\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["labels": [12, 34]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.coperniq.io/v1/projects/1/work-orders/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()
```