# Dashboard HTTP API

> **Reference** (informative) · **For:** HTTP client authors · **Source:** [OpenAPI description](https://github.com/Cotal-AI/Cotal/blob/main/implementations/web/openapi.json)

<!-- Generated by website/scripts/generate-openapi-docs.mjs. Edit implementations/web/openapi.json. -->

HTTP interface of the @cotal-ai/web reference implementation. This description covers seven read operations; it does not establish a separate stable product or Cloud management API. Start the dashboard using the web service preset described in the operator guide. The server defaults to 127.0.0.1:7799; use the host and port configured for your dashboard.

Open the owner-private launch URL once to exchange its k query token for the cotal_web_session cookie. Keep the session on the same host. With the default bind, the permitted browser origins are http://127.0.0.1:<port>, http://localhost:<port> and http://cotal.localhost:<port>. A custom host uses its own HTTP origin. A cross-origin request is refused before session checks. The readiness nonce only opens /api/meta.

All documented operations use GET and take no request body. Some read handlers also accept other bodyless methods; this description does not promise a 405 response for them. Internal readiness and channel deletion are outside this read reference. The native web service is read-only. Description version 1 identifies this document, not the wire or npm package version.

## Reading the contract

The [interactive reference](https://docs.cotal.ai/api/dashboard/) and [downloadable OpenAPI specification](https://docs.cotal.ai/dashboard.openapi.json) describe the same operations.

Schema references in the sections below resolve inside the downloadable specification.

See [API reference](/api-reference/) for the other integration interfaces.

## List the roster

`GET /api/roster`

Return the dashboard observer's current presence roster. Rows can include offline peers; freshness is reported separately by the presence-view SSE event.

### Parameters

No operation parameters.

### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "type": "array",
  "items": {
    "$ref": "#/components/schemas/RosterEntry"
  }
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Read channel membership

`GET /api/membership`

Return broker-sourced live and durable channel membership. asOf is absent when no membership heartbeat has been written. Use this for display, not authorization.

### Parameters

No operation parameters.

### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Membership"
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 503

The membership snapshot could not be read. A missing snapshot is not an empty successful one.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/MembershipError"
}
```

## List chat channels

`GET /api/channels`

List chat channels with message counts and effective replay and delivery policies. Event channels are excluded. description and replayWindow are absent when unset.

### Parameters

No operation parameters.

### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "type": "array",
  "items": {
    "$ref": "#/components/schemas/Channel"
  }
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Read recent activity

`GET /api/activity`

Merge the newest chat messages selected by stream sequence with recent direct messages, order by message timestamp and retain the newest limit. partial, missing and reasons identify sources that failed or exceeded the shared 8000 ms deadline. A partial page is still HTTP 200. Failure to obtain the channel list is HTTP 500.

### Parameters

| Name | Location | Required | Schema |
|---|---|---|---|
| limit | query | no | `{"type":"integer","minimum":0,"maximum":9007199254740991,"default":200}` |

**limit:** Maximum requested messages. Absent or empty uses 200. Accepts decimal digits naming a safe integer, including 0; negative, fractional, whitespace-padded and unsafe values are refused.


### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/ActivityPage"
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 400

The query or percent-encoded channel name is invalid.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Read direct-message history

`GET /api/dms`

Return recent direct messages visible to the dashboard observer. A failed or late read returns HTTP 503 instead of a partial array.

### Parameters

| Name | Location | Required | Schema |
|---|---|---|---|
| limit | query | no | `{"type":"integer","minimum":0,"maximum":9007199254740991,"default":500}` |

**limit:** Maximum requested messages. Absent or empty uses 500. Accepts decimal digits naming a safe integer, including 0; negative, fractional, whitespace-padded and unsafe values are refused.


### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "type": "array",
  "items": {
    "$ref": "#/components/schemas/Message"
  }
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 400

The query or percent-encoded channel name is invalid.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 503

The history read failed or did not finish within 8000 ms.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Read channel history

`GET /api/channels/{channel}/history`

Percent-decode the channel once and validate it using the core channel grammar. Invalid names are refused without rewriting them to another channel. A failed or late read returns HTTP 503.

### Parameters

| Name | Location | Required | Schema |
|---|---|---|---|
| channel | path | yes | `{"type":"string","minLength":1,"maxLength":4096}` |
| limit | query | no | `{"type":"integer","minimum":0,"maximum":9007199254740991,"default":200}` |

**channel:** Channel name or pattern accepted by the core channel grammar. Encode it as one URL path component.


**limit:** Maximum requested messages. Absent or empty uses 200. Accepts decimal digits naming a safe integer, including 0; negative, fractional, whitespace-padded and unsafe values are refused.


### Responses

#### HTTP 200

The current snapshot or history page.

Content type: `application/json`.

```json
{
  "type": "array",
  "items": {
    "$ref": "#/components/schemas/Message"
  }
}
```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 400

The query or percent-encoded channel name is invalid.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 503

The history read failed or did not finish within 8000 ms.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

## Watch live dashboard events

`GET /feed`

Open a Server-Sent Events stream. Each frame has event: <name> and data: <JSON>, followed by a blank line. The initial frames contain roster and presence-view, then membership or membership-read-failed. Later frames carry roster, presence-view, membership, membership-read-failed and message updates. The stream provides no event IDs or replay cursor; reconnect and read history for backfill. An unhandled request failure after headers have been sent closes the stream. Membership read failures are sent as events and leave the stream open. The schemas under x-sse-events describe decoded data payloads.

| Event | JSON data |
|---|---|
| `roster` | Array of presence records, with optional dashboard progress. |
| `presence-view` | `state` and `fresh`, plus `staleSince` for a stale observer. |
| `membership` | Optional `asOf` and an array of `members` with live and durable channels. |
| `membership-read-failed` | An object with a `reason` string. |
| `message` | `mode` and `msg`, with subject-derived `senderId` and chat `channel` when available. |

Decoded payload schemas are also included under `x-sse-events` in the [downloadable specification](https://docs.cotal.ai/dashboard.openapi.json).

The message event forwards publisher JSON without wire-message validation. Treat msg.from as advisory; senderId is derived from the broker subject. A malformed publisher payload can still appear in the feed.

### Parameters

No operation parameters.

### Responses

#### HTTP 200

An open UTF-8 SSE stream. Authentication and origin checks run before streaming begins.

`Cache-Control`: {"type":"string","const":"no-cache"}

Content type: `text/event-stream`.

```json
{
  "type": "string"
}
```

Example:

```text
event: roster
data: []

event: presence-view
data: {"state":"current","fresh":true}

```

#### HTTP 302

A valid one-use k query token is exchanged for a dashboard session. The response redirects to the same path without its query. A valid existing cookie takes precedence over k.

`Location`: The requested path without the query string.

`Set-Cookie`: cotal_web_session=<session>; Path=/; HttpOnly; SameSite=Strict. The default local listener uses HTTP.

#### HTTP 401

No valid session, an invalid launch token, or a spent launch token. The readiness credential does not authorize these routes.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/AuthenticationError"
}
```

Example:

```json
{
  "error": "unauthenticated"
}
```

#### HTTP 403

Origin is checked before session authentication. An origin outside this listener's allowed origins is refused.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/OriginError"
}
```

Example:

```json
{
  "error": "cross-origin"
}
```

#### HTTP 413

These read routes accept no request body. An announced body is refused before it is read; the connection is closed.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

#### HTTP 500

The request failed. The body names the failure; a failed read does not terminate the dashboard.

Content type: `application/json`.

```json
{
  "$ref": "#/components/schemas/Error"
}
```

### Event payloads

Each named event carries the following JSON data schema.

```json
{
  "roster": {
    "type": "array",
    "items": {
      "$ref": "#/components/schemas/RosterEntry"
    }
  },
  "presence-view": {
    "$ref": "#/components/schemas/PresenceView"
  },
  "membership": {
    "$ref": "#/components/schemas/Membership"
  },
  "membership-read-failed": {
    "type": "object",
    "properties": {
      "reason": {
        "type": "string"
      }
    },
    "required": [
      "reason"
    ]
  },
  "message": {
    "$ref": "#/components/schemas/LiveMessage"
  }
}
```

## Schemas

These schemas describe the HTTP request and response wrappers. The downloadable specification includes the referenced definitions.

### Message

```json
{
  "$ref": "#/components/schemas/WireCotalMessage"
}
```

### RosterEntry

```json
{
  "allOf": [
    {
      "$ref": "#/components/schemas/WirePresence"
    },
    {
      "type": "object",
      "properties": {
        "progress": {
          "type": "object",
          "properties": {
            "kind": {
              "const": "unknown",
              "type": "string"
            }
          },
          "required": [
            "kind"
          ]
        }
      }
    }
  ],
  "description": "Presence plus an optional progress signal. Working rows currently report progress.kind as unknown because the dashboard has no session-store observer."
}
```

### Membership

```json
{
  "type": "object",
  "properties": {
    "asOf": {
      "type": "number"
    },
    "members": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "live": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "durable": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "observedAt": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "live",
          "durable",
          "observedAt"
        ]
      }
    }
  },
  "required": [
    "members"
  ]
}
```

### Channel

```json
{
  "type": "object",
  "properties": {
    "channel": {
      "type": "string"
    },
    "messages": {
      "type": "integer",
      "minimum": 0
    },
    "description": {
      "type": "string"
    },
    "replay": {
      "type": "boolean"
    },
    "replayWindow": {
      "type": "string"
    },
    "deliveryClass": {
      "type": "string"
    }
  },
  "required": [
    "channel",
    "messages",
    "replay",
    "deliveryClass"
  ]
}
```

### ActivityEntry

```json
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "mode": {
          "const": "chat",
          "type": "string"
        },
        "channel": {
          "type": "string"
        },
        "msg": {
          "$ref": "#/components/schemas/Message"
        }
      },
      "required": [
        "mode",
        "channel",
        "msg"
      ]
    },
    {
      "type": "object",
      "properties": {
        "mode": {
          "const": "unicast",
          "type": "string"
        },
        "msg": {
          "$ref": "#/components/schemas/Message"
        }
      },
      "required": [
        "mode",
        "msg"
      ]
    }
  ]
}
```

### ActivityPage

```json
{
  "type": "object",
  "properties": {
    "entries": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/ActivityEntry"
      }
    },
    "partial": {
      "type": "boolean"
    },
    "read": {
      "type": "integer",
      "minimum": 0,
      "maximum": 2
    },
    "of": {
      "type": "integer",
      "const": 2
    },
    "missing": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "chat",
          "direct messages"
        ]
      }
    },
    "reasons": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "deadlineMs": {
      "type": "integer",
      "const": 8000
    }
  },
  "required": [
    "entries",
    "partial",
    "read",
    "of",
    "missing",
    "reasons",
    "deadlineMs"
  ]
}
```

### Error

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string"
    }
  },
  "required": [
    "error"
  ]
}
```

### AuthenticationError

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string",
      "enum": [
        "unauthenticated",
        "launch-token-already-used"
      ]
    }
  },
  "required": [
    "error"
  ]
}
```

### OriginError

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string",
      "const": "cross-origin"
    }
  },
  "required": [
    "error"
  ]
}
```

### MembershipError

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string",
      "const": "membership-read-failed"
    },
    "reason": {
      "type": "string"
    }
  },
  "required": [
    "error",
    "reason"
  ]
}
```

### PresenceView

```json
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "state": {
          "const": "current",
          "type": "string"
        },
        "fresh": {
          "const": true,
          "type": "boolean"
        }
      },
      "required": [
        "state",
        "fresh"
      ]
    },
    {
      "type": "object",
      "properties": {
        "state": {
          "const": "unpopulated",
          "type": "string"
        },
        "fresh": {
          "const": false,
          "type": "boolean"
        }
      },
      "required": [
        "state",
        "fresh"
      ]
    },
    {
      "type": "object",
      "properties": {
        "state": {
          "const": "stale",
          "type": "string"
        },
        "fresh": {
          "const": false,
          "type": "boolean"
        },
        "staleSince": {
          "type": "number"
        }
      },
      "required": [
        "state",
        "fresh",
        "staleSince"
      ]
    }
  ]
}
```

### LiveMessage

```json
{
  "type": "object",
  "properties": {
    "mode": {
      "type": "string",
      "enum": [
        "chat",
        "unicast",
        "anycast"
      ]
    },
    "senderId": {
      "type": "string"
    },
    "channel": {
      "type": "string"
    },
    "msg": {
      "description": "Unvalidated publisher JSON forwarded by the dashboard tap. Well-formed wire messages use the Message schema in this description. Consumers must validate the payload before treating it as a wire message; msg.from is advisory and senderId comes from the broker subject."
    }
  },
  "required": [
    "mode",
    "msg"
  ]
}
```
