# Conversations

> Reference for the /v1/rooms endpoints, which save chat conversations and their messages on the data plane — list, create, rename, end and delete conversations, and page through or add messages.

Conversations, called rooms in the API, store chat history on the data plane so a client can reload it. The Mortar console uses them for its sidebar; you can use them to keep a transcript next to the model that produced it.

Conversations are owned by whoever creates them. Those created with an API key belong to the key's organization and are visible to every key in it; those created in the console belong to the signed-in person.

> [!WARNING]
> Conversations are kept in the memory of the data-plane replica and are cleared when it restarts. Each owner can keep 200 conversations and each conversation keeps its latest 1,000 messages. Keep your own copy of anything you can't lose.

## The conversation object

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | `room-` followed by 8 hexadecimal characters. |
| `headerTitle` | string | The title shown above the conversation. |
| `headerSubtitle` | string | The line under the title, such as the model and environment. |
| `sidebarTitle` | string | The title shown in a list of conversations. |
| `sidebarSubtitle` | string | A preview of the latest message, up to 80 characters. |
| `meta` | string | A short label for when the conversation last changed, such as `Now`. |
| `badge` | object | Optional. A label with `type` and `label`. |
| `hasEnded` | boolean | Whether the conversation was marked as ended. |

## The message object

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | The message ID. |
| `type` | string | `message` for anything added through the API. |
| `roomId` | string | The conversation it belongs to. |
| `senderId` | string | Who sent it, such as `you` or `mortar`. |
| `senderName` | string | The sender's display name. |
| `content` | string | The message text. |
| `timestamp` | string | When it was sent, as an ISO 8601 time in UTC. |
| `reactions` | object | Emoji reactions: each key is an emoji and its value lists who reacted. |
| `attachments` | array | Files shown with the message, each with `id`, `name` and `meta`. |
| `isDeleted` | boolean | Whether the message was deleted. |
| `isSelected` | boolean | Whether the message is selected in the console. |
| `replyTo` | string or null | The ID of the message this one answers. |
| `requestId` | string | Only on a message saved with one: the `id` of the chat completion it is the reply from. |
| `usage` | object | Only on a message saved with a `requestId`: what that completion was billed, attached by Mortar. See [the usage object](#the-usage-object). |

## The usage object

What a reply's completion was billed: Mortar's own settlement of the request, the figures the [`cost.update` telemetry event](/docs/api/telemetry#reading-cache-and-cost-events) reports. Mortar attaches it to the message that names the completion in `requestId`; a `usage` you send is ignored. A completion's usage waits 30 minutes for its message, and a message as long for its completion, so you can save a stopped reply at once and its usage follows when the stream has closed. A message saved without a `requestId`, or later than that, has no `usage`. Amounts are US dollars at [sample pricing](/models), sent to 12 significant digits.

| Field | Type | Description |
| --- | --- | --- |
| `requestId` | string | The completion's `id`. |
| `model` | string | The display name of the model that answered. |
| `modelId` | string | The model's ID, as the request named it. |
| `finishReason` | string | `stop`, `length`, `tool_calls` or the model server's own reason; `stopped` when the reply was stopped or its client left; `error` when the model server failed after the reply had begun. |
| `usageSource` | string | `upstream` when the counts are the model server's own, `estimate` when Mortar counted them. |
| `promptTokens` | integer | Tokens in the prompt. |
| `completionTokens` | integer | Tokens in the reply. |
| `cacheReadTokens` | integer | Prompt tokens read from the prefix cache. |
| `cacheWriteTokens` | integer | Prompt tokens prefilled and written to the cache. |
| `inputUsd` | number | The prompt's cost, cache reads and writes included. |
| `outputUsd` | number | The reply's cost. |
| `totalUsd` | number | `inputUsd` plus `outputUsd`. |
| `baselineUsd` | number | What the same request costs with no prefix cache. |
| `savedUsd` | number | `baselineUsd` minus `totalUsd`. Negative on a turn that only wrote the cache. |
| `ttftMs` | integer | Milliseconds to the first token: measured when a model server answered, Mortar's simulated figure otherwise. |
| `durationMs` | integer | Milliseconds from Mortar receiving the request to the reply's end. |

## List conversations

`GET /v1/rooms`

Lists your conversations, oldest first. Needs an API key.

```bash title="Request"
curl https://api.mortar.one/v1/rooms \
  -H "Authorization: Bearer $MORTAR_API_KEY"
```

```json title="Response" output
{
  "object": "list",
  "data": [
    {
      "id": "room-9a8b7c6d",
      "headerTitle": "Capacity planning",
      "headerSubtitle": "Qwen 2.5 7B Instruct · Managed · ap-south-1",
      "sidebarTitle": "Capacity planning",
      "sidebarSubtitle": "Which GPU should I run a 32B model on?",
      "meta": "Now",
      "hasEnded": false
    }
  ]
}
```

## Create a conversation

`POST /v1/rooms`

Starts an empty conversation. Needs an API key.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | No | The conversation's title. Defaults to `New conversation`. |
| `subtitle` | string | No | The line under the title. Defaults to the model and environment this deployment serves. |

### Request headers

| Header | Required | Description |
| --- | --- | --- |
| `X-Mortar-Session` | No | A telemetry session to notify. Mortar publishes `room.created`, `room.updated` and `room.deleted` events to [that session's stream](/docs/api/telemetry#stream-serving-telemetry), which only your organization can follow. |

### Example

```bash title="Request"
curl https://api.mortar.one/v1/rooms \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Release notes"}'
```

```json title="Response" output
{
  "id": "room-3fa85f64",
  "headerTitle": "Release notes",
  "headerSubtitle": "Qwen 2.5 7B Instruct · Managed · ap-south-1",
  "sidebarTitle": "Release notes",
  "sidebarSubtitle": "No messages yet",
  "meta": "Now",
  "hasEnded": false
}
```

The response status is `201`. With 200 conversations already saved, the request fails with `409` and the code `room_limit_reached`; delete one to make room.

## Add a message

`POST /v1/rooms/{id}/messages`

Saves a message to a conversation and makes its text the conversation's preview. Mortar stores the message as you send it; it doesn't generate a reply. Needs an API key.

### Path parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `id` | string | The conversation's ID. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | string | Yes | The message text. |
| `id` | string | No | Your own ID for the message. Defaults to `msg-` and 8 random hexadecimal characters. |
| `senderId` | string | No | Who sent it. Defaults to `you`. |
| `senderName` | string | No | The sender's display name. Defaults to `You`. |
| `timestamp` | string | No | When it was sent. Defaults to now, as an ISO 8601 time in UTC. |
| `reactions` | object | No | Emoji reactions, keyed by emoji. |
| `attachments` | array | No | Files to show with the message, each with `id`, `name` and `meta`. |
| `replyTo` | string | No | The ID of the message this one answers. |
| `requestId` | string | No | The `id` of the chat completion this message is the reply from. Mortar attaches what that completion was billed as [`usage`](#the-usage-object). |

### Example

```bash title="Request"
curl https://api.mortar.one/v1/rooms/room-3fa85f64/messages \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Summarise the changes from this week."}'
```

```json title="Response" output
{
  "id": "msg-7d1c2b9e",
  "type": "message",
  "roomId": "room-3fa85f64",
  "senderId": "you",
  "senderName": "You",
  "content": "Summarise the changes from this week.",
  "timestamp": "2026-10-29T09:30:00.000Z",
  "reactions": {},
  "attachments": [],
  "isDeleted": false,
  "isSelected": false,
  "replyTo": null
}
```

The response status is `201`.

### Save a reply with its usage

Name the completion a reply came from, and the saved message carries what it was billed.

```bash title="Request"
curl https://api.mortar.one/v1/chat/completions \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"stream": false, "max_tokens": 200, "messages": [{"role": "user", "content": "What does max_tokens limit?"}]}'
```

```json title="Response" output
{
  "id": "chatcmpl-5d8e1f4a7b0c3e6d9a2b5c8f",
  "object": "chat.completion",
  "created": 1767225600,
  "model": "Qwen/Qwen2.5-7B-Instruct",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "It caps how many tokens the model may generate for this reply."
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 32,
    "completion_tokens": 13,
    "total_tokens": 45
  }
}
```

```bash title="Request"
curl https://api.mortar.one/v1/rooms/room-3fa85f64/messages \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"senderId": "mortar", "senderName": "Qwen 2.5 7B Instruct", "content": "It caps how many tokens the model may generate for this reply.", "requestId": "chatcmpl-5d8e1f4a7b0c3e6d9a2b5c8f"}'
```

```json title="Response" output
{
  "id": "msg-2e4a6c8b",
  "type": "message",
  "roomId": "room-3fa85f64",
  "senderId": "mortar",
  "senderName": "Qwen 2.5 7B Instruct",
  "content": "It caps how many tokens the model may generate for this reply.",
  "timestamp": "2026-10-29T09:30:05.000Z",
  "reactions": {},
  "attachments": [],
  "isDeleted": false,
  "isSelected": false,
  "replyTo": null,
  "requestId": "chatcmpl-5d8e1f4a7b0c3e6d9a2b5c8f",
  "usage": {
    "requestId": "chatcmpl-5d8e1f4a7b0c3e6d9a2b5c8f",
    "model": "Qwen 2.5 7B Instruct",
    "modelId": "Qwen/Qwen2.5-7B-Instruct",
    "finishReason": "stop",
    "usageSource": "estimate",
    "promptTokens": 32,
    "completionTokens": 13,
    "cacheReadTokens": 0,
    "cacheWriteTokens": 32,
    "inputUsd": 0.000001252,
    "outputUsd": 0.0000030849,
    "totalUsd": 0.0000043369,
    "baselineUsd": 0.0000040865,
    "savedUsd": -0.0000002504,
    "ttftMs": 140,
    "durationMs": 152
  }
}
```

The figures are those of the completion's [`cost.update` event](/docs/api/telemetry#reading-cache-and-cost-events). Its first turn wrote its prompt to the cache, so `savedUsd` is negative.

## List messages

`GET /v1/rooms/{id}/messages`

Pages backwards through a conversation: each page holds the newest messages before a point, in the order they were sent. Needs an API key.

### Path parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `id` | string | The conversation's ID. |

### Query parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many messages to return, up to 200. Defaults to 50. |
| `before` | string | A message ID. Returns the messages sent before it. Leave it out for the newest page. |

### Example

```bash title="Request"
curl "https://api.mortar.one/v1/rooms/room-3fa85f64/messages?limit=20" \
  -H "Authorization: Bearer $MORTAR_API_KEY"
```

```json title="Response" output
{
  "object": "list",
  "data": [
    {
      "id": "msg-7d1c2b9e",
      "type": "message",
      "roomId": "room-3fa85f64",
      "senderId": "you",
      "senderName": "You",
      "content": "Summarise the changes from this week.",
      "timestamp": "2026-10-29T09:30:00.000Z",
      "reactions": {},
      "attachments": [],
      "isDeleted": false,
      "isSelected": false,
      "replyTo": null
    }
  ],
  "hasMore": false
}
```

### Response

| Field | Type | Description |
| --- | --- | --- |
| `object` | string | Always `list`. |
| `data` | array | [Message objects](#the-message-object), oldest first. Empty when the conversation has no messages, or none before `before`. |
| `hasMore` | boolean | Whether older messages exist. Pass the first message's `id` as `before` to fetch them. |

## Update a conversation

`PATCH /v1/rooms/{id}`

Renames a conversation or marks it as ended. Fields you leave out are unchanged. Needs an API key.

### Path parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `id` | string | The conversation's ID. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | No | A new title. An empty title leaves the old one. |
| `hasEnded` | boolean | No | `true` to mark the conversation as ended, `false` to reopen it. |

### Example

```bash title="Request"
curl -X PATCH https://api.mortar.one/v1/rooms/room-3fa85f64 \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Release notes, week 44", "hasEnded": true}'
```

```json title="Response" output
{
  "id": "room-3fa85f64",
  "headerTitle": "Release notes, week 44",
  "headerSubtitle": "Qwen 2.5 7B Instruct · Managed · ap-south-1",
  "sidebarTitle": "Release notes, week 44",
  "sidebarSubtitle": "Summarise the changes from this week.",
  "meta": "Now",
  "hasEnded": true
}
```

## Delete a conversation

`DELETE /v1/rooms/{id}`

Deletes a conversation and all of its messages. This can't be undone. Needs an API key.

### Path parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `id` | string | The conversation's ID. |

### Example

```bash title="Request"
curl -X DELETE https://api.mortar.one/v1/rooms/room-3fa85f64 \
  -H "Authorization: Bearer $MORTAR_API_KEY"
```

```json title="Response" output
{
  "deleted": true,
  "id": "room-3fa85f64"
}
```

## Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | No conversation with that ID belongs to you. |
| 409 | `room_limit_reached` | You already have 200 conversations. |
| 400 | | The body isn't valid JSON. |
