title: Update per-thread user state
keywords:

- update per-thread user state
- patch
- update
- per-thread
- user
- state


# Update per-thread user state

Merge-patch the per-thread user state. Only fields present in the request body are updated; omitted fields are left unchanged. Send an explicit null to clear a field.

#### Path Parameters

**`thread_id`** string required

The thread id parameter.

#### Request Body

**`model_override`** string optional

The model this chat uses instead of the assistant default; `auto` lets Auto pick per message. Changing it from `auto` to a model is recorded as feedback on the chat's latest Auto choice.

**`auto_choice`** string optional

The Auto priority for this chat: `balanced`, `faster`, `lower_cost` or `best_quality`; `null` follows your own default. It applies only where the rules let you choose. Setting a priority returns 409 `AUTO_MODEL_CHOICE_LOCKED` where a rule decides it for you (`error.details.locked_by` names the organisation, department or assistant, `error.details.reason` its reason) and nothing is written; 404 `AUTO_MODEL_NOT_ENABLED` while Auto is not available to you. `null` is always accepted.

**`reasoning_override`** string optional

The reasoning override parameter.

**`temperature_override`** number optional

Per-thread sampling temperature override (`0.0`–`1.0`). Merge-patch semantics: omit to leave unchanged, send `null` to clear (use the assistant/model default), send a value to set. When set, it applies to subsequent turns on this thread unless the response request itself specifies `temperature` (request value wins). Note: some models (e.g. reasoning-first models) ignore sampling temperature entirely.

**`visual_density_override`** string optional

Per-thread visual-density override for the chat UI. Merge-patch semantics: omit to leave unchanged, send `null` to clear (use the user/global default), send a value to set.

**`disabled_tools`** array of string optional

The disabled tools parameter.

**`tool_conflict_choices`** object optional

The tool conflict choices parameter.

**`workspace_layout`** object optional

How this thread is arranged — which side the chat occupies and what is open beside it — so the arrangement survives a refresh and a trip to another thread. Merge-patch semantics: omit to leave unchanged, send `null` to clear (back to the full-width chat with nothing pinned). Replaced **wholesale** rather than merged, because a half-applied layout (docked with no pane, or a pane with no side) is not a state a client should have to render.

Fields: `dock` (`left` | `right` | `null`), `panes` (array of `{ kind, item_id, title }` — everything open beside the chat, **nearest the chat first**, where `kind` is `document` and `item_id` is the item to reopen; at most 4, and anything beyond that is dropped from the far end), `pane` (a mirror of `panes[0]`, or `null`), `set_at` (UTC timestamp), `set_by` (`user` | `assistant`).

Send either `pane` or `panes` — the other is filled in for you, so a client that only ever opens one thing beside the chat can keep sending `pane` and nothing changes. If you send both and they disagree, `panes` wins: it is the field that can describe the whole arrangement, so a `pane` contradicting it is a stale mirror rather than a second opinion.

`set_at` is dated for you: omit it and the API records the time of this write. Send one only when you are preserving a timestamp you already have — a layout with no date makes the age of the arrangement unanswerable, which is what the `set_by` rule below turns on.

`set_by` is the field a "restore layout" control should read: a layout the **user** arranged can be restored silently, while one the **assistant** arranged was a suggestion made on one turn and is better offered behind a visible control than reimposed. `pane.item_id` is re-resolved and access-rechecked on restore — storing it does not grant access to it.

## Returns

**`thread_id`** string

Thread Id

**`state`** object

The state.

Example

```bash cURL
curl -X PATCH "https://api.aitronos.com/v1/threads/{thread_id}/user-state" \
  -H "X-API-Key: $FREDDY_API_KEY" \
  -H "Content-Type: application/json"
```


```python Python
import os
import requests

api_key = os.environ["FREDDY_API_KEY"]

response = requests.patch(
    "https://api.aitronos.com/v1/threads/{thread_id}/user-state",
    headers={"X-API-Key": api_key},
)

print(response.json())
```


```javascript JavaScript
const apiKey = process.env.FREDDY_API_KEY;

const response = await fetch('https://api.aitronos.com/v1/threads/{thread_id}/user-state', {
  method: 'PATCH',
  headers: {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json'
  }
});

const data = await response.json();
console.log(data);
```

**Response:**


```json 200 OK
{
  "thread_id": "thread_abc123def456",
  "state": {}
}
```

Errors

```json 401 Unauthorized
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required. Please provide a valid API key.",
    "system_message": "Missing or invalid authorization header",
    "type": "authentication_error",
    "status": 401,
    "trace_id": "req_abc123xyz",
    "timestamp": "2025-11-11T10:30:00Z",
    "details": {}
  }
}
```


```json 403 Forbidden
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "You do not have permission to access this resource.",
    "system_message": "Insufficient permissions for this operation",
    "type": "authorization_error",
    "status": 403,
    "trace_id": "req_def456uvw",
    "timestamp": "2025-11-11T10:30:00Z",
    "details": {}
  }
}
```

## Related Resources

- [List Threads](/docs/api-reference/threads/list)
- [Retrieve Thread](/docs/api-reference/threads/retrieve)
- [Update Thread](/docs/api-reference/threads/update)
- [Stream Thread Live](/docs/api-reference/threads/live)
- [Get Per-Thread User State](/docs/api-reference/threads/get-per-thread-user-state)
- [Save Open Document Tabs](/docs/api-reference/threads/save-artifact-tabs)