# ICD-API — Chat Switchboard Backend API Contract

**Version:** 0.28.0
**Updated:** 2026-03-11
**Audience:** Frontend developers, integrators, API consumers

> **Organization principle:** Each file is one domain — the complete story.
> Every endpoint that touches Knowledge Bases is in `knowledge.md`, every
> endpoint that touches Personas is in `personas.md`, etc.

## Files

| File | Domain | Endpoints |
|------|--------|-----------|
| [auth.md](auth.md) | Auth — login, register, refresh, OIDC, mTLS | 6 |
| [channels.md](channels.md) | Channels, messages, completions, participants, folders, presence | ~35 |
| [personas.md](personas.md) | Personas, persona groups, avatars, KB bindings, tool grants | ~20 |
| [knowledge.md](knowledge.md) | Knowledge bases, documents, search, channel/persona bindings | ~12 |
| [notes.md](notes.md) | Notes, wikilinks, graph, search, folders | ~10 |
| [workspaces.md](workspaces.md) | Workspaces, file ops, git, credentials | ~20 |
| [projects.md](projects.md) | Projects, channel/KB/note associations | ~14 |
| [memory.md](memory.md) | Memory extraction, review, admin | 7 |
| [providers.md](providers.md) | Provider configs, health, capabilities, routing policies | ~25 |
| [models.md](models.md) | Enabled models, user preferences | 4 |
| [notifications.md](notifications.md) | Notifications, preferences | 7 |
| [extensions.md](extensions.md) | Extensions, user settings, admin | 8 |
| [profile.md](profile.md) | User profile, avatar, password, app settings | 7 |
| [teams.md](teams.md) | Teams, members, groups, permissions, grants | ~25 |
| [admin.md](admin.md) | Platform admin — users, settings, stats, audit, usage, vault | ~25 |
| [utility.md](utility.md) | Health, export, files, storage | ~12 |
| [workflows.md](workflows.md) | Workflow definitions, stages, instances, assignments, entry | ~20 |
| [tasks.md](tasks.md) | Task definitions, runs, scheduler, team tasks | ~14 |
| [surfaces.md](surfaces.md) | Surface registry, extension surfaces | 6 |
| [websocket.md](websocket.md) | WebSocket protocol, events, rooms | — |
| [enums.md](enums.md) | All enum values, policies | — |

## Conventions

### Base URL

```
{scheme}://{host}{BASE_PATH}/api/v1
```

`BASE_PATH` is empty by default. In path-routed K8s deployments (e.g.
`/staging/`), all routes shift accordingly. The frontend reads `BASE_PATH`
from a `<meta>` tag injected by the Go template engine.

### Authentication

JWT bearer tokens. Every request to a `protected` or `admin` route requires:

```
Authorization: Bearer {access_token}
```

**Access token:** HS256 JWT, 15-minute TTL. Claims: `user_id`, `email`,
`role`, `exp`, `iat`, `jti`.

**Refresh token:** Opaque, DB-stored, 7-day TTL. Used to obtain new
access tokens without re-login.

**WebSocket fallback:** `?token={access_token}` query parameter when
the `Authorization` header isn't available.

**Cookie sync:** On every token save/refresh, the frontend writes
`sb_token` as a cookie. Go template page routes read this cookie via
`AuthOrRedirect` middleware for server-rendered surfaces.

### Auth Modes

| Mode | `AUTH_MODE` | How it works |
|------|------------|--------------|
| Builtin | `builtin` (default) | Username/password, bcrypt, JWT |
| mTLS | `mtls` | Reverse proxy cert headers, auto-provision |
| OIDC | `oidc` | Authorization code flow (Keycloak etc.) |

All three modes issue the same JWT after authentication. Downstream
middleware is auth-mode-agnostic.

### Authorization Tiers

| Tier | Middleware | Who |
|------|-----------|-----|
| Public | none | Anyone (health, login, register, public settings) |
| Session | `AuthOrSession()` | JWT user OR anonymous session cookie (workflow visitors) |
| Authenticated | `Auth()` | Any logged-in user |
| Permission | `RequirePermission(perm)` | User whose groups grant `perm` |
| Team Admin | `RequireTeamAdmin()` | Admin of the specific team |
| Platform Admin | `RequireAdmin()` | Users with `role = "admin"` |

### Error Envelope

All errors return:

```json
{ "error": "human-readable message" }
```

Standard HTTP status codes: 400 (bad request), 401 (not authenticated),
403 (not authorized), 404 (not found), 409 (conflict), 500 (server error),
502 (upstream provider failure).

### Response Envelopes

Three patterns. Every endpoint uses exactly one.

**List (returns an array)** → always wrap in `{"data": []}`:

```json
{
  "data": [...]
}
```

If paginated, pagination fields sit alongside `data`:

```json
{
  "data": [...],
  "page": 1,
  "per_page": 50,
  "total": 142
}
```

Query params: `?page=1&per_page=50`. Not all list endpoints paginate —
unpaginated lists still use `{"data": [...]}`.

This is a hard rule: `data` is the only array wrapper key. No
domain-specific keys (`teams`, `members`, `configs`, etc.) for arrays.
Clients parse every list response identically.

**Single object (GET by ID, profile, health)** → return the object directly:

```json
{
  "id": "uuid",
  "name": "...",
  ...
}
```

No wrapping. The response *is* the resource.

**Composite (multiple named values, not an array)** → named keys:

```json
{
  "active": 5,
  "pending": 2
}
```

Used for stats, counts, status endpoints — anything returning
multiple named scalars or heterogeneous data. The key names are
domain-specific and documented per endpoint.

**Empty arrays** must serialize as `[]`, never `null`. Go handlers
must guard nil slices before serialization:

```go
if result == nil {
    result = []MyType{}
}
c.JSON(http.StatusOK, gin.H{"data": result})
```

### ID Format

All resource IDs are UUIDv4 strings, generated application-side via
`store.NewID()` (Go `uuid.New()`). This ensures compatibility across
both Postgres and SQLite backends.

### Timestamps

ISO 8601 with timezone: `"2025-06-15T14:30:00Z"`. Stored as
`TIMESTAMPTZ` (Postgres) or `TEXT` (SQLite).

## Page Routes (Non-API)

Server-rendered Go template surfaces. Not REST endpoints — return HTML.

| Route | Surface | Description |
|-------|---------|-------------|
| `/login` | Login | Standalone login/register page |
| `/` | Chat | Main chat interface |
| `/chat/:chatID` | Chat | Chat with specific channel loaded |
| `/editor` | Editor | Workspace file editor |
| `/editor/:wsId` | Editor | Editor with specific workspace |
| `/notes` | Notes | Notes interface |
| `/notes/:noteId` | Notes | Notes with specific note loaded |
| `/admin` | Admin | Platform administration |
| `/admin/:section` | Admin | Admin with specific section |
| `/settings` | Settings | User settings |
| `/settings/:section` | Settings | Settings with specific section |
| `/w/:id` | Workflow Landing | Branded workflow entry page |
| `/w/:id/:slug` | Workflow Landing | Slug-suffixed workflow entry |
| `/s/:slug` | Extension Surface | Dynamic surface from registry |

All page routes (except `/login`, `/w/`) require authentication via
`AuthOrRedirect` middleware. Admin routes additionally require
`RequireAdminPage()`. Workflow landing pages use `AuthOrSession`.
