> This page is for Workspace app.

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

# Retitle, relink, invite into, set the work status of or archive a work session

PATCH https://api.cloudraker.com/workspace/sessions/{sessionId}
Content-Type: application/json

Changes the title, description, linked spaces, participants or work status, or archives the session. `workStatus` (`active`, `ready_for_review`, `done`) needs only `work_session:comment`, but `done` must come from a person: an org API key, a trigger delivery is 403 `work_status_needs_person`, and so is `archive: true` from any of them, since archiving marks the work `done`. Archiving sets `done`, a reopen `active`, and a new agent turn moves it back to `active`. **Authorization:** `work_session:comment` on the session, OR `space:contribute` on one of its linked spaces; a desk session is writable only by its owner. Any denial is 404. `spaces`, `removeHumans` and `archive` need MORE than that: they are the owner’s (`work_session:manage`, spec §8.1), so a participant or a space contributor gets 403 `forbidden` on those three fields and keeps `title`, `description` and `addHumans`. Every id in `spaces` is re-checked as the caller exactly as `POST /workspace/sessions` checks `spaceIds`: a `me:*` desk the session is not ALREADY on is 400 `cannot_target_desk` (moving a session onto a desk would hide it from its own opener) and a space that does not exist or the caller cannot contribute to is 404, so a relay can never move a session somewhere its owner loses it. Each entry is `{ spaceId, access }` (`access` defaults to `commit`) or a bare id string, and `spaces` replaces the whole set, so it needs at least one entry — a session with no space cannot host a turn, and an empty array left it un-mentionable with no way back. `fileIds` replaces the working set the same way — every id is resolved as the caller (404 for an unknown or unreadable one) and `[]` empties it, so a session whose set carries a file nobody can read is repairable instead of permanently un-mentionable. A relink is checked against the working set it MOVES: every file the session already runs on must live in the new turn space, so a `spaces` patch that would leave the session un-mentionable is 404 — send `fileIds` in the same call (`[]` empties the set) to move and refill at once. The body is STRICT: an unknown key is 400 `validation_failed`, never a 200 that changed nothing. Every id in `addHumans` must be an active member of the caller’s organization: an id from another organization, or one that names nobody, is 404, so the roster never lists a person who could not open the session. `addHumans` also grants each member the `work-session-participant` FGA role, and `removeHumans` revokes it — agents are never FGA subjects (a turn runs as the human who mentioned it). A DESK session cannot be shared: the gate authorizes it by identity alone, so `addHumans` on one is 400 `cannot_share_desk` — relink it to a real space first. (A session opened with no `spaceIds` lands on the desk, so this is the default create path’s sharing story.)

Reference: https://docs.cloudraker.com/workspace/api/workspace-sessions/patch-workspace-session

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Request

### Path parameters

- `sessionId` (string, required)

### Body (application/json)

This endpoint expects a PatchWorkspaceSessionBody.

- `title` (string, optional)
- `description` (string, optional)
- `spaces` (list of WorkspaceSpaceLink, optional)
- `addHumans` (list of string, optional)
- `removeHumans` (list of string, optional)
- `fileIds` (list of string, optional) — The session’s working set, replaced wholesale. Every id must exist and be readable by the caller; `[]` empties the set.
- `archive` (boolean, optional)
- `workStatus` (enum, optional) — Whether the WORK is done, which only people decide. `status` stays the derived state of the turns.
  - Allowed values: `active`, `ready_for_review`, `done`

## Response

### 200

The updated work session.

- `data` (WorkspaceSession, required)

## Errors

### 400 Bad Request Error

`cannot_target_desk` — a `me:*` id the session is not already linked to; `cannot_share_desk` — `addHumans` on a desk session; `validation_failed` — an unknown key in the body.

- `any`

### 403 Forbidden Error

`forbidden` — `spaces`, `removeHumans` or `archive` without `work_session:manage`. `work_status_needs_person` — `workStatus: done` or `archive: true` from anything but a person.

- `any`

### 404 Not Found Error

No such session, the caller may not write it, no such space, a file in the working set that does not exist, the caller cannot read, or that does not live in the turn space this patch leaves the session in, or an `addHumans` id that is not a member of this organization.

- `any`

### 409 Conflict Error

`session_has_live_turn` — a turn is still running, so the session cannot be archived. Cancel it first. `session_archived` — the title, description or work status of archived work cannot change; reopen it (`archive: false`) in the same or an earlier call.

- `any`

## Types

### WorkspaceSpaceLink

- string
- WorkspaceSpaceLink1

### WorkspaceSession

- `id` (string, required)
- `title` (string, required)
- `description` (string, required)
- `status` (enum, required)
  - Allowed values: `open`, `working`, `waiting`, `done`, `archived`
- `workStatus` (enum, required) — Whether the WORK is done, which only people decide. `status` stays the derived state of the turns.
  - Allowed values: `active`, `ready_for_review`, `done`
- `openedBy` (WorkSessionSender, required)
- `openedVia` (enum, required)
  - Allowed values: `web`, `desktop`, `api`, `email`, `webhook`, `schedule`, `slack`, `assistant`
- `participants` (WorkspaceSessionParticipants, required)
- `spaces` (list of WorkspaceSessionSpacesItems, required)
- `files` (list of WorkspaceSessionFilesItems, required)
- `triggerId` (string, required, nullable)
- `parentSessionId` (string, required, nullable)
- `counts` (WorkspaceSessionCounts, required)
- `lastActivityAt` (datetime, required)
- `createdAt` (datetime, required)
- `closedAt` (string, required, nullable)

### WorkspaceSpaceLink1

- `spaceId` (string, required)
- `access` (enum, optional, default: commit)
  - Allowed values: `read`, `commit`

### WorkSessionSender

- `kind` (enum, required)
  - Allowed values: `user`, `agent`, `api_key`, `trigger`, `external`
- `id` (string, required)
- `name` (string, required)
- `avatarUrl` (string, optional)
- `source` (enum, optional)
  - Allowed values: `web`, `desktop`, `api`, `email`, `webhook`, `schedule`, `slack`, `assistant`, `manual`, `trigger`

### WorkspaceSessionParticipants

- `humans` (list of string, required)
- `agents` (list of WorkspaceSessionParticipantsAgentsItems, required)

### WorkspaceSessionSpacesItems

- `spaceId` (string, required)
- `access` (enum, required)
  - Allowed values: `read`, `commit`

### WorkspaceSessionFilesItems

- `fileId` (string, required)
- `role` (enum, required)
  - Allowed values: `input`, `artifact`

### WorkspaceSessionCounts

- `turns` (double, required)
- `approvalsPending` (double, required)
- `tasksPending` (double, required)
- `unread` (double, required)

### WorkspaceSessionParticipantsAgentsItems

- `agentId` (string, required)
- `versionId` (string, required)
- `handle` (string, required)
- `name` (string, required)
- `role` (string, required)
- `joinedAt` (datetime, required)

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "data": {
    "id": "string",
    "title": "string",
    "description": "string",
    "status": "open",
    "workStatus": "active",
    "openedBy": {
      "kind": "user",
      "id": "string",
      "name": "string",
      "avatarUrl": "string",
      "source": "web"
    },
    "openedVia": "web",
    "participants": {
      "humans": [
        "string"
      ],
      "agents": [
        {
          "agentId": "string",
          "versionId": "string",
          "handle": "string",
          "name": "string",
          "role": "string",
          "joinedAt": "2024-01-15T09:30:00Z"
        }
      ]
    },
    "spaces": [
      {
        "spaceId": "string",
        "access": "read"
      }
    ],
    "files": [
      {
        "fileId": "string",
        "role": "input"
      }
    ],
    "triggerId": "string",
    "parentSessionId": "string",
    "counts": {
      "turns": 1.1,
      "approvalsPending": 1.1,
      "tasksPending": 1.1,
      "unread": 1.1
    },
    "lastActivityAt": "2024-01-15T09:30:00Z",
    "createdAt": "2024-01-15T09:30:00Z",
    "closedAt": "string"
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.cloudraker.com/workspace/sessions/sessionId"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript
const url = 'https://api.cloudraker.com/workspace/sessions/sessionId';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

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.cloudraker.com/workspace/sessions/sessionId"

	payload := strings.NewReader("{}")

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

	req.Header.Add("Authorization", "Bearer <token>")
	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.cloudraker.com/workspace/sessions/sessionId")

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

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

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.cloudraker.com/workspace/sessions/sessionId")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.cloudraker.com/workspace/sessions/sessionId', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/workspace/sessions/sessionId");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/workspace/sessions/sessionId")! 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()
```