# courier.config.ts

Every field of courier.config.ts: the sources, the API imports Studio keeps in sync, Studio, the standalone server, the runtime and what agents may call.

## Overview

`courier.config.ts`, at the project root, names the project's API sources and what the tools around them need. `defineConfig` types it:

```ts title="courier.config.ts"
import { defineConfig } from "@noverax/courier/config"
import { api } from "./api/sources"

export default defineConfig({
  sources: { api },
  studio: { port: 6280 },
  server: { allowedOrigins: ["https://app.example.com"] },
  runtime: { appOrigin: "http://localhost:3000" },
})
```

Studio reads it, and so do the Studio runtime (`attachRuntime()`) and the standalone server (`courier-server`) — both of which run in production. So the file imports nothing meant only for development: mock servers are declared in `courier.mocks.ts` beside it, which only Studio reads — see [Mock Servers](/docs/studio/mock-servers).

The file may also be named `courier.config.mts`, `.cts`, `.js`, `.mjs` or `.cjs`, looked for in that order after `.ts`. [How Studio Loads the Project](/docs/studio/project-loading) says which files Studio watches and how a change reloads them.

## sources

**Required.** The sources the project serves and calls, by the name its callers use: `sources: { api }` is reached through the proxy as `createProxyCaller(apiRoutes)`, from the routes Courier writes for it. A project without an API yet starts with `sources: {}`.

## imports

The API specifications Studio imported, by key. Studio writes an entry when it imports a specification and reads it on every re-sync, so an entry is usually Studio's to write; editing one by hand is supported.

| Field | Type | |
| --- | --- | --- |
| `type` | `"openapi"` \| `"swagger"` \| `"graphql"` \| `"asyncapi"` \| `"postman"` | The specification's format, which picks its parser |
| `importType` | `"url"` \| `"file"` \| `"raw"` | Where it came from. A file's path and a pasted specification are supplied in Studio at each re-sync, never stored |
| `from` | `string` | `url` only: the address Studio fetches it from |
| `auth` | `{ type: "bearer", token }` \| `{ type: "basic", username, password }` \| `{ type: "header", headers }` | `url` only: sent with every fetch of `from`, to its own origin only |
| `pinHash` | `string` | `url` only: the one specification the import is generated from, by the hash Studio records; another is refused |
| `outputDir` | `string` | The directory that holds the import's own directory, `<outputDir>/<source-name>/` |
| `sourceName` | `string` | The source's name in the generated `defineApi` call — default: the key |
| `validation` | `"zod"` \| `"valibot"` \| `"arktype"` \| `"none"` | The schema library the generated code uses. With ArkType, a reference cycle of more than about 40 schemas — Stripe's core objects form one of 83 — goes past TypeScript's depth limit where an endpoint answers with one of them (TS2321); Zod and Valibot type such a specification |
| `groupBy` | `"tag"` \| `"path"` \| `"single"` | How endpoints are grouped: by tag, by the resource their path names, or in no group at all. An endpoint with no tag, or with no segment of its path to group by, sits in the API itself, beside the groups — see [Import an API](/docs/studio/import-api) |
| `generateTypes`, `generateSchemas` | `boolean` | Whether types and schemas are written |
| `generateClient` | `"direct"` \| `"proxy"` \| `"both"` \| `"none"` | Which callers are generated: the direct caller in the import's `index.ts`, the proxy caller in `proxy.ts`, which browser code imports |
| `addAuthMiddleware` | `boolean` | Whether the specification's security scheme becomes middleware |
| `includeComments` | `boolean` | Whether descriptions become doc comments. Each endpoint keeps its operation's description as `.meta({ description })` either way: an agent's tool describes itself by it, and the OpenAPI written from the definition keeps it |
| `include`, `exclude` | `{ tags?, paths?, operationIds? }` | The endpoints generated: `include` names the ones kept, then `exclude` the ones left out |
| `transform` | `(spec) => spec \| void` | Run on a copy of the parsed specification before anything is generated from it |

See [Import an API](/docs/studio/import-api).

## studio

What Studio reads: `port`, `host`, `mockBasePort` and `open` when it starts, `allowedSpecHosts` whenever a fetch would reach a private host, and `routes` each time the config loads. A value Studio cannot use — a port that is not a whole number from 1 to 65535, an empty host, an `open` that is not `true` or `false`, a `routes` path outside the project — fails the config, and its error names the field.

| Field | Default | |
| --- | --- | --- |
| `port` | `6280` | The port Studio serves on; `--port` overrides it |
| `host` | `"localhost"` | The address it binds; `--host` overrides it |
| `mockBasePort` | `7001` | Where the ports Studio assigns to mocks that name none begin |
| `open` | `false` | Open Studio in the browser when it starts; `--open` and `--no-open` override it |
| `allowedSpecHosts` | — | Hosts on a private network Studio may fetch a specification from, such as `["gitlab.internal", "10.0.0.7:8080"]`. This machine needs no entry |
| `routes` | `"courier.routes.ts"` | Where the [routes module](/docs/proxy-mode/expose) goes, from the project root: a `.ts`, `.mts`, `.js` or `.mjs` file in the project, such as `"src/courier.routes.ts"`. Beside a JavaScript config it is `courier.routes.js`; `false` keeps the project without one. Studio writes it whenever the config loads with an endpoint marked `.expose()`, and `courier-cli routes` without Studio; neither writes over a file Courier did not write |

## server

What the [standalone server](/docs/deployment/standalone-server) reads when its command-line flags say nothing.

| Field | Default | |
| --- | --- | --- |
| `port` | `3001` | `--port` overrides it |
| `host` | `"127.0.0.1"` | `--host` overrides it; `"0.0.0.0"` or `"::"` to accept other machines |
| `allowedOrigins` | — | The origins of the browser apps that call the server, written as a browser sends them. Left out, only the server's own origin; outside production a page on another port of the local machine is let through as well, so Studio can drive the app, but never reads the answers — list the origin of a dev server whose page calls this one. `--allowed-origins` overrides it |
| `allowedHosts` | — | The host names the server is reached by; a request to another is refused with `421`. `--allowed-hosts` overrides it |
| `maxBodyBytes` | 1 MB | The largest JSON envelope the proxy reads; a larger one is refused with `413` |
| `maxUploadBytes` | 10 MB | The largest `multipart/form-data` upload the proxy reads |

## runtime

What `attachRuntime()` needs in the application's process.

| Field | |
| --- | --- |
| `appOrigin` | The origin the application listens on, such as `http://localhost:3000`, where Studio's Playground sends its proxy calls — requests, streams and channels alike. Looked up in turn: this field, `COURIER_APP_URL`, `APP_URL`, then, in development, the address the server tells of itself: see [How the app learns its origin](#app-origin). A deployed server never learns one. With none of them, a Playground call in proxy mode fails with `APP_ORIGIN_NOT_CONFIGURED` |

### How the app learns its origin <a id="app-origin"></a>

In development, with no `appOrigin`, `COURIER_APP_URL` or `APP_URL`, the app learns its origin from the server itself, never from a header a request sends, so a reverse proxy, a tunnel, a container or a `*.localhost` name in front of it needs no setting:

- **Next.js**, a Hono app inside a Next.js route among them: the address `next dev` records as it starts listening, `https` only with its own certificate, before any request.
- **Express, Fastify, NestJS, Hono on Node or Bun, SolidStart:** the address and port of the socket the first request to a Courier route arrived on. Until a Courier route has served a request, a Playground call has nowhere to go: open a page that calls your API first.
- **The [standalone server](/docs/deployment/standalone-server):** the address it listens on, as it starts.
- **SvelteKit, Astro, React Router, Nuxt:** the address Studio's Vite plugin or Nuxt module sees the dev server start listening on, before any request. Under an app's own server that runs Vite in middleware mode, as React Router's custom server does, the socket of the first request that server hands Vite.

Set `COURIER_APP_URL` where Studio cannot see the server — Deno, Cloudflare Workers, a server on a UNIX socket, or an app that routes by the name it is reached at — to the address the app's own process reaches its proxy route at: in a container, the container's own address and port, not the port the host publishes; behind a tunnel, the local address, not the public one, which would send the Playground's calls and their credentials through the tunnel's provider.

## agents

What the project offers to AI agents as MCP tools. The server in your editor, Studio and `courier-studio diff` read it here; an application's own MCP endpoint takes it as `createMcpHandler({ agents: config.agents })`. Left out, the endpoints marked `.agent()` are offered and nothing else.

| Field | Default | |
| --- | --- | --- |
| `offer` | `"marked"` | Which endpoints are offered without `.agent()`: `"marked"` — none; `"reads"` — every read; `"all"` — every endpoint, with the access its verb implies |
| `allow` | — | Patterns of endpoints to offer although `offer` leaves them out: `["shop.orders.*"]` |
| `deny` | — | Patterns of endpoints no agent sees, whatever offers them: `["billing.**"]` |
| `access` | `"destructive"` | The most a tool may do: `"read"`, `"write"` or `"destructive"`; an endpoint that does more is offered to no agent |
| `confirm` | `"destructive"` | From which access a person confirms each call: `"destructive"`, or `"write"` for every call that changes something |

A pattern names an endpoint by its call under its source; `*` stands for any part of one name, `**` for any number of names. A key or a value the policy does not know stops the MCP server with `AGENT_POLICY_INVALID` — a misspelt key under `agents` is named when Studio loads the config, too. See [What agents may call](/docs/agents/overview#what-agents-may-call).
