# API Reference

All endpoints under `/api/v1/` require `Authorization: Bearer <token>` (validated by ows-grass upstream) unless noted otherwise.

## Health

### GET /health

Liveness probe. No authentication required.

```json
{ "status": "ok", "service": "ows-coda" }
```

### GET /health/ready

Readiness probe. No authentication required. Returns `503 Service Unavailable` until the Bedrock prompt cache is seeded.

```json
{ "status": "ready" }
```

## Models

### GET /api/v1/models

Lists available AI models.

**Response 200:**

```json
[
  {
    "id": "claude-sonnet-4-6",
    "name": "Claude Sonnet 4.6",
    "vendor": "Anthropic",
    "provider": "bedrock",
    "maxTokens": 200000,
    "isDefault": true
  }
]
```

## Conversations

### GET /api/v1/chats

Lists all conversations for the authenticated user. Returns a cursor-paginated envelope.

**Query parameters:**

| Param     | Type    | Default | Description                                       |
| --------- | ------- | ------- | ------------------------------------------------- |
| `limit`   | number  | 50      | Max items per page                                |
| `before`  | string  | —       | Cursor: fetch the page ending before this cursor  |
| `after`   | string  | —       | Cursor: fetch the page starting after this cursor |
| `starred` | boolean | —       | Filter by starred status                          |

**Response 200:**

```json
{
  "data": [
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "title": "Revenue Q4 2025",
      "starred": false,
      "createdAt": "2026-03-01T12:00:00.000Z",
      "updatedAt": "2026-03-01T12:05:00.000Z"
    }
  ],
  "pageInfo": {
    "startCursor": "eyJ...",
    "endCursor": "eyJ...",
    "hasPreviousPage": false,
    "hasNextPage": true
  }
}
```

### POST /api/v1/chats

Creates a new conversation.

**Request body:**

```json
{ "title": "optional string" }
```

**Response 201:** `Conversation` object.

### DELETE /api/v1/chats

Bulk delete multiple conversations.

**Request body:**

```json
{ "ids": ["uuid-1", "uuid-2"] }
```

**Response 200:**

```json
{ "status": "ok", "deleted": 2 }
```

### GET /api/v1/chats/:id/messages

Retrieves message history for a conversation. Returns a cursor-paginated envelope.

**Query parameters:**

| Param    | Type   | Default | Description                                                 |
| -------- | ------ | ------- | ----------------------------------------------------------- |
| `limit`  | number | 50      | Max items per page                                          |
| `before` | string | —       | Cursor: fetch the page ending before this cursor            |
| `after`  | string | —       | Cursor: fetch the page starting after this cursor           |
| `leaf`   | string | —       | Client's cached leaf message ID for branch-aware delta sync |

**Response 200:**

```json
{
  "data": [
    {
      "id": "msg-uuid",
      "chatId": "chat-uuid",
      "depth": 0,
      "role": "user",
      "text": "What is the gross revenue for account 12345?",
      "sources": [],
      "attachments": [],
      "thinkingSteps": []
    }
  ],
  "pageInfo": {
    "startCursor": "eyJ...",
    "endCursor": "eyJ...",
    "hasPreviousPage": false,
    "hasNextPage": true
  }
}
```

### POST /api/v1/chats/:id/stream

Main SSE streaming chat endpoint.

**Request body:**

```json
{
  "query": "What is the gross revenue for account 12345 in 2024?",
  "model": "claude-sonnet-4-6",
  "attachments": [
    {
      "url": "data:image/png;base64,<b64>",
      "mediaType": "image/png",
      "filename": "screenshot.png"
    }
  ]
}
```

- `query` — required, max 2000 characters
- `model` — optional, defaults to `claude-sonnet-4-6`
- `attachments` — optional, up to 2 files, 25 MB each

**SSE event stream:**

| Event                | Payload                                                  | Description                           |
| -------------------- | -------------------------------------------------------- | ------------------------------------- |
| `message_start`      | `{ userMessageId, assistantMessageId, model }`           | Stream start with message IDs         |
| `chunk`              | `{ chunk: string }`                                      | Streamed text token                   |
| `progress`           | `{ steps: ConversationStep[] }` or `{ done: true }`      | Tool-call progress                    |
| `reasoning`          | `{ chunk: string }` or `{ done: true }`                  | Extended thinking                     |
| `selection_required` | `{ items, searchTerm }`                                  | Disambiguation                        |
| `clear`              | `{}`                                                     | Discard partial text                  |
| `compression`        | `{ messageCount: number }` or `{ done: true }`           | Context compression progress          |
| `sources`            | `SourceLink[]`                                           | Entity deep-links                     |
| `suggestions`        | `string[]`                                               | Follow-up questions                   |
| `attachments`        | `ExtractedAttachment[]`                                  | Generated files                       |
| `usage`              | `{ inputTokens, outputTokens, totalTokens, latencyMs? }` | Token usage                           |
| `title`              | `{ title: string }`                                      | Auto-generated title (first exchange) |
| `warnings`           | `string[]`                                               | Attachment warnings                   |
| `done`               | `{ done: true, messageId, userMessageId, reason }`       | Stream complete                       |
| `error`              | `{ error, message }`                                     | Fatal error                           |

### PATCH /api/v1/chats/:id

Updates conversation metadata.

**Request body:**

```json
{ "title": "optional string", "starred": true }
```

**Response 200:** Updated `Conversation` object.
**Response 404:** Conversation not found.

### DELETE /api/v1/chats/:id

Deletes a conversation and all its messages.

**Response 200:**

```json
{ "status": "ok" }
```

### GET /api/v1/chats/deleted

Returns IDs of soft-deleted conversations for offline sync.

**Response 200:**

```json
{ "ids": ["uuid-1", "uuid-2"] }
```

### GET /api/v1/chats/:id/attachments/:attachmentId/url

Returns a pre-signed URL for downloading an attachment.

**Response 200:**

```json
{ "url": "https://s3.amazonaws.com/..." }
```

### GET /api/v1/me/permissions

Returns the authenticated user's effective permission set.

**Response 200:**

```json
{
  "permissions": ["tools.snowflake.query", "chat.create", "models.sonnet.use"],
  "isSuperAdmin": false
}
```

## Tool Execution

### POST /api/v1/tools/execute

Executes a single tool by name. Used by the MCP server to proxy tool calls through the existing auth/permission stack. See the [MCP server guide](../guides/mcp-server.md) for details.

**Request body:**

```json
{
  "name": "search_accounts",
  "input": { "search_term": "Sony" }
}
```

| Field   | Type   | Required | Description                              |
| ------- | ------ | -------- | ---------------------------------------- |
| `name`  | string | Yes      | Tool name from the tool catalog          |
| `input` | object | No       | Tool input parameters (defaults to `{}`) |

**Response 200 (success):**

```json
{
  "data": { "accounts": [{ "id": 123, "name": "Sony Music" }] },
  "error": null
}
```

**Response 200 (tool error):**

```json
{
  "data": null,
  "error": "Unknown tool: bad_tool"
}
```

**Response 400:** Missing or invalid `name` field.
**Response 500:** Unexpected server error during tool execution.

## Resource relationships

```mermaid
erDiagram
    User ||--o{ Conversation : "owns"
    Conversation ||--o{ ConversationMessage : "contains"
    ConversationMessage ||--o{ ToolCall : "executes"
    ConversationMessage ||--o{ ThinkingStep : "reasons"
    ConversationMessage ||--o{ SourceLink : "cites"
    ConversationMessage ||--o{ Attachment : "includes"
    ConversationMessage ||--o| Feedback : "rated by"

    User {
        string id "from identity header"
    }

    Conversation {
        string id "UUID"
        string title "auto-generated"
        boolean starred
        string createdAt "ISO-8601"
        string updatedAt "ISO-8601"
    }

    ConversationMessage {
        string id "UUID"
        string role "user | assistant"
        string content "message text"
        int depth "tree position"
        object usage "tokens and latency"
    }

    ToolCall {
        string name "tool identifier"
        object input "varies per tool"
        object result "varies per tool"
        string status "success | error"
    }

    SourceLink {
        string href "platform deep-link"
        string title "display label"
    }

    Attachment {
        string type "image | document | file"
        string mediaType "MIME type"
        string filename
        int sizeBytes
    }

    Feedback {
        boolean rating "thumbs up or down"
        string comment "optional"
    }
```

## Types

```typescript
interface Conversation {
  id: string; // UUID
  title: string | null;
  starred: boolean;
  createdAt: string; // ISO-8601
  updatedAt: string; // ISO-8601
}
```

## Rate limiting

| Scope       | Limit       | Applies to                      |
| ----------- | ----------- | ------------------------------- |
| General API | 100 req/min | All `/api/` routes              |
| Streaming   | 10 req/min  | `POST /api/v1/chats/:id/stream` |

Both limits are per user, enforced via Redis sliding window with in-memory fallback.

## Supported models

| Frontend key                  | Bedrock model ID                               |
| ----------------------------- | ---------------------------------------------- |
| `claude-sonnet-4-6` (default) | `us.anthropic.claude-sonnet-4-6`               |
| `claude-opus-4-6`             | `us.anthropic.claude-opus-4-6-v1`              |
| `claude-sonnet-3-5`           | `us.anthropic.claude-3-5-sonnet-20241022-v2:0` |

Unknown model keys silently fall back to the configured default.

## Supported attachment types

| MIME type                                                                 | Bedrock type |
| ------------------------------------------------------------------------- | ------------ |
| `image/jpeg`, `image/png`, `image/gif`, `image/webp`                      | image        |
| `application/pdf`, `text/csv`, `text/html`, `text/plain`, `text/markdown` | document     |
| `application/msword`, `.docx`, `.xls`, `.xlsx`                            | document     |

Up to **2 attachments** per request, **25 MB** each.
