# Configuration reference

> Environment variables and project configuration: INTELLIGENCE_API_KEY, CHANNEL_CODE, PORT, the paired INTELLIGENCE_API_URL / INTELLIGENCE_GATEWAY_WS_URL overrides (bare base URLs, never derived from each other), AGENT_URL for remote AG-UI agents, plus the required tsconfig (jsxImportSource, module settings) and package.json shape (type: module, overrides pin).

- Repository: CopilotKit/channels-sdk
- GitHub: https://github.com/CopilotKit/channels-sdk
- Human docs: https://grok-wiki.com/public/docs/copilotkit-channels-sdk-4c947e3e6161
- Complete Markdown: https://grok-wiki.com/public/docs/copilotkit-channels-sdk-4c947e3e6161/llms-full.txt

## Source Files

- `.agents/skills/build-channels-agent/SKILL.md`
- `examples/minimal-channel/.env.example`
- `examples/minimal-channel/lib/env.ts`
- `examples/minimal-channel/tsconfig.json`
- `README.md`

---

---
title: "Configuration reference"
description: "Environment variables and project configuration: INTELLIGENCE_API_KEY, CHANNEL_CODE, PORT, the paired INTELLIGENCE_API_URL / INTELLIGENCE_GATEWAY_WS_URL overrides (bare base URLs, never derived from each other), AGENT_URL for remote AG-UI agents, plus the required tsconfig (jsxImportSource, module settings) and package.json shape (type: module, overrides pin)."
---

A Channels process reads its configuration from environment variables at startup and fails fast when a required one is missing. Three variables cover the standard managed setup (`INTELLIGENCE_API_KEY`, `CHANNEL_CODE`, `PORT`), two paired variables override the hosted Intelligence endpoints for self-hosted or non-production environments (`INTELLIGENCE_API_URL`, `INTELLIGENCE_GATEWAY_WS_URL`), and `AGENT_URL` points at a remote AG-UI agent when the agent runs outside the listener process. Beyond environment variables, the project itself must be ESM (`"type": "module"`), pin a single copy of `@ag-ui/client` via `overrides`, and — in any project that renders Channels JSX — point the TypeScript JSX factory at `@copilotkit/channels` with `jsxImportSource`.

## Environment variables

| Variable | Required | Purpose |
| --- | --- | --- |
| `INTELLIGENCE_API_KEY` | Yes | Project-scoped CopilotKit Intelligence API key |
| `CHANNEL_CODE` | Yes (managed Channels) | The exact Channel Code from Intelligence, passed as `createChannel({ name })` |
| `PORT` | No (defaults to `3000`) | Port for the lifecycle HTTP server |
| `INTELLIGENCE_API_URL` | Only for self-hosted / non-production | Bare base URL of the Intelligence HTTP API |
| `INTELLIGENCE_GATEWAY_WS_URL` | Only for self-hosted / non-production | Bare base URL of the Intelligence gateway WebSocket |
| `AGENT_URL` | Only for remote AG-UI agents | HTTP URL of an AG-UI-compatible agent, consumed by `HttpAgent` |
| `OPENAI_API_KEY` | Only when using `BuiltInAgent` with an OpenAI model | Model provider credential for the built-in agent |

<ParamField body="INTELLIGENCE_API_KEY" type="string" required>
  The project-scoped API key that authenticates the process to CopilotKit Intelligence. Create it from **API Keys** in the Intelligence project sidebar. A key is required for every Channel — there is no standalone or DIY way to run one. Keep `.env` out of source control and never put this key (or any platform token) in browser code.
</ParamField>

<ParamField body="CHANNEL_CODE" type="string" required>
  Must equal the Channel Code exactly as shown in Intelligence; pass it as the `name` option of `createChannel`. Codes are 3–64 characters, start with a lowercase letter, use lowercase letters and digits separated by single hyphens, are unique per project, and are never the literal `channels`. The value is validated by the runtime, not by `createChannel` — a typo fails at startup and leaves the Channel at **Waiting for runtime** in the dashboard rather than erroring at the call site.
</ParamField>

<ParamField body="PORT" type="number" default="3000">
  Port for the HTTP server created around the listener. Managed message delivery arrives over the Channel's own gateway socket, not this port — but keep the server: it serves the runtime's web requests, and most hosts require a listening port for health checks. The reference listener reads it as `Number(process.env.PORT ?? 3000)`.
</ParamField>

<ParamField body="AGENT_URL" type="string">
  HTTP URL of a remote AG-UI-compatible agent. Pass it to `HttpAgent` from `@ag-ui/client` (also re-exported from `@copilotkit/channels`) and hand that to `createChannel({ agent })`. The built-in agent (`BuiltInAgent` from `@copilotkit/runtime/v2`) runs in the same Node process and needs no `AGENT_URL` and no second server.
</ParamField>

<ParamField body="OPENAI_API_KEY" type="string">
  Only needed when the agent factory constructs `BuiltInAgent` with an OpenAI model (for example `new BuiltInAgent({ model: "openai:gpt-5.4-mini" })`). A remote AG-UI agent owns its own model credentials.
</ParamField>

### Paired Intelligence URL overrides

Hosted Intelligence supplies both endpoint defaults, so a standard managed Channel sets **neither** variable. Override them only for self-hosted or non-production Intelligence — and always **both together**:

<ParamField body="INTELLIGENCE_API_URL" type="string">
  Bare base URL of the Intelligence HTTP API, passed as `apiUrl` to `new CopilotKitIntelligence({...})`. Example: `https://intelligence.example.com`.
</ParamField>

<ParamField body="INTELLIGENCE_GATEWAY_WS_URL" type="string">
  Bare base URL of the Intelligence gateway WebSocket, passed as `wsUrl` to `new CopilotKitIntelligence({...})`. Example: `wss://realtime.intelligence.example.com`.
</ParamField>

<Warning>
The API host and the gateway host are **separate hosts**. Never derive `wsUrl` from `apiUrl` by swapping `https://` for `wss://` — override both or neither. Pass bare base URLs only: the client appends `/api/...`, `/runner`, `/client`, or `/channels` itself, so appending path segments yourself breaks the connection.
</Warning>

```ts title="Wiring the overrides into CopilotKitIntelligence"
import { CopilotKitIntelligence } from "@copilotkit/runtime/v2";

const intelligence = new CopilotKitIntelligence({
  apiKey: required("INTELLIGENCE_API_KEY"),
  // Both optional; hosted Intelligence supplies the defaults.
  apiUrl: process.env.INTELLIGENCE_API_URL,
  wsUrl: process.env.INTELLIGENCE_GATEWAY_WS_URL,
});
```

### Example .env files

<CodeGroup>

```dotenv title=".env — managed Channel (hosted Intelligence)"
INTELLIGENCE_API_KEY=<project-api-key>
CHANNEL_CODE=support-slack
PORT=3000

# Optional paired overrides for self-hosted or non-production Intelligence:
# INTELLIGENCE_API_URL=https://intelligence.example.com
# INTELLIGENCE_GATEWAY_WS_URL=wss://realtime.intelligence.example.com
```

```dotenv title=".env — remote AG-UI agent (examples/minimal-channel)"
AGENT_URL=<http-url-of-your-ag-ui-agent>
INTELLIGENCE_API_URL=<intelligence-api-url>
INTELLIGENCE_GATEWAY_WS_URL=<intelligence-gateway-ws-url>
INTELLIGENCE_API_KEY=<project-api-key>
```

</CodeGroup>

## Loading environment variables

The documented start command loads `.env` through Node's native flag — no dotenv dependency required:

```sh
node --env-file=.env --import tsx channel.ts
```

The `examples/minimal-channel` project instead loads `dotenv` in `lib/env.ts` (honoring a `DOTENV_CONFIG_PATH` override) and wraps every read in a fail-fast helper, so a missing variable stops the process at import time instead of producing a half-configured Channel:

```ts title="examples/minimal-channel/lib/env.ts"
import { config } from "dotenv";

config({ path: process.env.DOTENV_CONFIG_PATH, quiet: true });

export const required = (name: string) => {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
};
```

Either approach works; the invariant is that required variables are asserted before `createChannel` and `CopilotKitIntelligence` consume them.

<Note>
A Channel needs **Node.js 22+** (the launcher requires global `WebSocket`) and a long-running process. A serverless request handler cannot host one — the process must own a persistent gateway connection.
</Note>

## tsconfig requirements

Any project that renders Channels JSX (`<Message>`, `<Button>`, …) must compile JSX against Channels, not React. Files containing JSX must use the `.tsx` extension, and the tsconfig must set `jsxImportSource`:

```json title="tsconfig.json — Channels project with JSX"
{
  "compilerOptions": {
    "target": "ES2022",
    "jsx": "react-jsx",
    "jsxImportSource": "@copilotkit/channels",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "types": ["node"]
  }
}
```

Constraints that matter:

- **`jsxImportSource: "@copilotkit/channels"`** — without it, the JSX tree compiles against React and fails. Point it at the package you actually installed; do **not** point it at `@copilotkit/channels-ui` unless that package is a direct dependency. It is only a transitive dependency of the umbrella package, so the import resolves under npm's hoisted layout but fails under pnpm's isolated one.
- **`module` / `moduleResolution`** — `nodenext`/`nodenext` for a Node-executed project. The `examples/minimal-channel` project (which posts no JSX) uses `"module": "ESNext"` with `"moduleResolution": "Bundler"`, `"noEmit": true`, and a `"@/*"` path alias instead; both are valid ESM shapes.
- **`strict: true`** — the SDK's typed surfaces (the branded non-optional `listener.channels`, handler return types, `awaitChoice<T>`) are exercised under strict mode in every shipped example.

## package.json shape

Three properties are load-bearing:

```json title="package.json — required shape"
{
  "type": "module",
  "dependencies": {
    "@copilotkit/channels": "0.6.1",
    "@copilotkit/runtime": "1.65.0"
  },
  "overrides": {
    "@ag-ui/client": "0.0.57"
  }
}
```

### type: module

The listener uses top-level `await` (`await channels.ready(...)`), so the project must be ESM. Set it with:

```sh
npm pkg set type=module
```

Without it, compilation fails with `TS1309: The current file is a CommonJS module`.

### Version-locked package pair

`@copilotkit/channels` and `@copilotkit/runtime` ship together as a tested pair — install with `--save-exact` and upgrade both together. Known-good pairs are `0.6.1` + `1.65.0` and `0.7.1` + `1.66.1` (`defineChannelComponent` and the native-node helpers are 0.7+ only).

### The @ag-ui/client overrides pin

Channels and Runtime both depend on one exact `@ag-ui/client` version, but a transitive dependency (`@ag-ui/mcp-middleware`) pulls an older one, and npm nests it. Two copies mean two separate `AbstractAgent` declarations, so passing *any* agent to `createChannel({ agent })` fails with a confusing error about "separate declarations of a private property `_debug`". Pin one copy to the version Runtime declares (`npm ls @ag-ui/client` shows both), then reinstall:

<Tabs>
<Tab title="npm">

```json
{ "overrides": { "@ag-ui/client": "0.0.57" } }
```

</Tab>
<Tab title="pnpm">

```json
{ "pnpm": { "overrides": { "@ag-ui/client": "0.0.57" } } }
```

</Tab>
<Tab title="yarn">

```json
{ "resolutions": { "@ag-ui/client": "0.0.57" } }
```

</Tab>
</Tabs>

This duplicate is the single most likely reason a correct-looking Channel refuses to typecheck. The `examples/minimal-channel` project sidesteps it by declaring `@ag-ui/client` as a direct dependency at the matching version.

## Related pages

<CardGroup cols={2}>
  <Card title="Installation" href="/installation">
    The full install sequence: the version-locked package pair, `npm pkg set type=module`, the overrides pin, and tsconfig setup.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    Build the first managed Channel end to end, set the four environment variables, and start with `node --env-file`.
  </Card>
  <Card title="Minimal Channel example" href="/minimal-channel-example">
    The smallest complete listener file by file, including the fail-fast `lib/env.ts` loading pattern.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    The `_debug` private-property compile error, TS1309 from missing `type: module`, and other documented failure modes.
  </Card>
</CardGroup>
