> ## Documentation Index
> Fetch the complete documentation index at: https://dev-docs.persona.hasanraiyan.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Routes

> Every route an @personaai/express mount exposes, how the adapter translates Express requests, and which SDK call each route proxies to.

The adapter exposes the runtime's entire route table on whatever path you mount it at. The mount
is a single catch-all middleware: `app.use('/api/persona', toExpressRouter(runtime))` forwards
every request under `/api/persona/` to `runtime.handle()`. The runtime owns matching, params, and
404/405 (with `Allow`) — the adapter adds no routes of its own.

## Always-on routes (end-user scoped)

| Method | Path (relative to mount) | What it does |
| - | - | - |
| `POST` | `/chat` | Streams an agent response as SSE (AG-UI events); response carries `x-persona-run-id` |
| `GET` | `/chat/:runId/resume` | Reattaches to the run started by the matching `POST /chat` |
| `GET` | `/threads` | Lists the user's conversations |
| `POST` | `/threads` | Creates a thread |
| `POST` | `/threads/bulk-delete` | Deletes several threads at once |
| `GET` | `/threads/:id` | Gets a thread |
| `PATCH` | `/threads/:id` | Updates title / archive state |
| `DELETE` | `/threads/:id` | Deletes a thread (`204`) |
| `GET` | `/threads/:id/messages` | Full message history + graph state (resume source) |
| `GET` | `/agents` | Read-only agent discovery for your users |
| `GET` | `/files` | Lists the user's files |
| `POST` | `/files` | Uploads a file (multipart, `file` part) — `201` |
| `POST` | `/files/bulk-delete` | Deletes several files |
| `GET` | `/files/:id` | Downloads a file (binary) |
| `DELETE` | `/files/:id` | Deletes a file (`204`) |
| `GET` | `/memory` | Lists memory entries |
| `GET` | `/memory/file` | Reads a memory file (`path` query param) |
| `PUT` | `/memory/file` | Writes a memory file |
| `DELETE` | `/memory/file` | Deletes a memory file (`204`) |
| `GET` | `/mcps/:id/oauth/owner/authorize` | Owner OAuth authorize URL (redirect target) |
| `GET` | `/mcps/:id/oauth/user/authorize` | End-user OAuth authorize URL |
| `GET` | `/mcps/:id/oauth/user/status` | End-user MCP connection status |
| `DELETE` | `/mcps/:id/oauth/user/connection` | Disconnects the end user's connection (`204`) |
| `DELETE` | `/mcps/:id/oauth/owner/connection` | Disconnects the owner's connection (`204`) |
| `GET` | `/health` | Liveness + capability probe — **no auth required** |

Every route except `GET /health` requires an authenticated user (`401` when the resolver returns
`null` or throws). For the exact SDK call each route proxies to, see the
[Runtime routes reference](/guides/runtime/routes).

## Opt-in admin routes

The Project-level admin surface (agents CRUD, providers, skills, knowledge, stores, MCPs,
audit logs, architect) is off by default — it's gated behind `capabilities.*` on the runtime.
When a capability is disabled, its routes are simply absent (a request `404`s or `405`s, never
`403`). See [Runtime capabilities](/guides/runtime/capabilities).

To expose the admin surface, mount a *second* runtime with capabilities on and stricter auth:

```ts theme={null}
app.use('/api/persona', toExpressRouter(appRuntime));          // end users
app.use('/api/admin/persona', toExpressRouter(adminRuntime));  // capabilities: true, stricter resolveUser
```

## How the adapter translates a request

`toExpressRouter` maps an Express `Request` to the runtime's framework-neutral contract before
`runtime.handle()` runs. The behaviors to know:

| Concern | Behavior |
| - | - |
| **Path** | Uses `req.path` — automatically mount-relative (identical semantics in Express 4 and 5). The runtime's own `mountPath` option is *not* needed when mounting via the adapter. |
| **Query** | Parsed from `req.originalUrl` via `URLSearchParams`, so repeated keys collapse to the last value (`Record<string, string>`). |
| **Headers** | Copied verbatim; array-valued headers are joined with `, `. |
| **Method** | Uppercased; `GET`/`DELETE` are treated as bodyless. |
| **JSON bodies** | Uses `req.body` when a host body parser (`express.json()`) already ran, otherwise reads and parses the raw stream itself. Malformed JSON → `400 INVALID_REQUEST`. |
| **Multipart bodies** | Parsed natively (see [Uploads](/guides/express/uploads)); already-parsed `req.file`/`req.files` from multer are honored. |
| **Auth** | `resolveUserFrom` (if provided) runs after translation and stamps the result as the runtime request's `userId` (see [Auth](/guides/express/auth)). |

## Error responses

* Runtime responses — including sanitized error envelopes `{"error":{code,message}}` — are
  forwarded **verbatim** (status, headers, body).
* Adapter translation failures (malformed JSON, unparsable multipart) respond
  `400 INVALID_REQUEST` in the same envelope shape.
* Any other adapter-side error goes to `next(err)` so your normal Express error middleware handles
  it — unless headers are already sent, in which case the connection is ended cleanly.

See [Runtime errors](/guides/runtime/errors) for the two detail modes (`development` vs
`production`).
