diff --git a/.github/workflows/static_intelligence-env-names.yml b/.github/workflows/static_intelligence-env-names.yml
new file mode 100644
index 0000000000..081a5f325a
--- /dev/null
+++ b/.github/workflows/static_intelligence-env-names.yml
@@ -0,0 +1,41 @@
+name: static / intelligence env names
+
+# Deliberately unfiltered. A non-canonical Intelligence key name can appear in
+# any README, example, skill, or doc page, and the two workflows that would
+# otherwise cover this both filter paths — plugin-skills-check by `paths:` and
+# static/quality by `paths-ignore: examples/**`, which is precisely where the
+# deprecated alias still lives. Scoping this job would re-open the hole it
+# exists to close.
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+ with:
+ persist-credentials: false
+
+ - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
+
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 20
+ cache: pnpm
+
+ - run: pnpm install --frozen-lockfile
+
+ - name: Check Intelligence env var names are canonical
+ run: pnpm check:intelligence-env-names
diff --git a/dev-docs/architecture/setup-intelligence.md b/dev-docs/architecture/setup-intelligence.md
index 537e9e5b3f..82604ae791 100644
--- a/dev-docs/architecture/setup-intelligence.md
+++ b/dev-docs/architecture/setup-intelligence.md
@@ -69,7 +69,7 @@ npm install @copilotkit/runtime
import { CopilotKitIntelligence } from "@copilotkit/runtime";
const intelligence = new CopilotKitIntelligence({
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORGANIZATION_ID!,
});
```
@@ -84,7 +84,7 @@ layer appends `/runner` or `/client` itself.
```typescript
const intelligence = new CopilotKitIntelligence({
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORGANIZATION_ID!,
apiUrl: "https://api.your-intelligence-host",
wsUrl: "wss://realtime.your-intelligence-host",
diff --git a/examples/slack/.env.example b/examples/slack/.env.example
index 7abe4b9ca3..f7348a4beb 100644
--- a/examples/slack/.env.example
+++ b/examples/slack/.env.example
@@ -51,7 +51,8 @@ OPENAI_API_KEY=sk-...
# plus the channel name chosen in code).
# Project runtime API key (cpk-…). The only Intelligence value you need: the SDK
# defaults both URLs to the managed platform.
-COPILOTKIT_API_KEY=cpk-...
+# `COPILOTKIT_API_KEY` is a deprecated alias for this variable, still read as a fallback.
+INTELLIGENCE_API_KEY=cpk-...
# Optional, for a self-hosted or dev Intelligence deployment only. Set BOTH or
# neither: the realtime plane is a DIFFERENT host from the API (api.… vs
# realtime.…), so it cannot be derived from the API URL, and setting one alone
diff --git a/examples/slack/README.md b/examples/slack/README.md
index 42b6e151f3..0053bfec3c 100644
--- a/examples/slack/README.md
+++ b/examples/slack/README.md
@@ -119,7 +119,7 @@ bot.onMention(async ({ thread, message }) => {
// lifecycle — it starts the direct Slack adapter for us.
const intelligence = new CopilotKitIntelligence({
// apiUrl/wsUrl default to the managed Intelligence platform.
- apiKey: process.env.COPILOTKIT_API_KEY!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
});
const runtime = new CopilotRuntime({
agents: {}, // the Channel supplies its own agent
@@ -332,7 +332,7 @@ several from one process).
```bash
cp .env.example .env
# Fill in (set SLACK_*, DISCORD_*, and/or TELEGRAM_BOT_TOKEN — whichever you want):
-# COPILOTKIT_API_KEY (REQUIRED — owns the Channel; free tier)
+# INTELLIGENCE_API_KEY (REQUIRED — owns the Channel; free tier)
# SLACK_BOT_TOKEN / SLACK_APP_TOKEN (to run on Slack)
# DISCORD_BOT_TOKEN / DISCORD_APP_ID (to run on Discord; DISCORD_GUILD_ID optional)
# TELEGRAM_BOT_TOKEN (to run on Telegram)
@@ -342,8 +342,9 @@ cp .env.example .env
# NOTION_MCP_AUTH_TOKEN (any strong string; shared between the sidecar and the agent)
```
-A Channel runs only through the Intelligence runtime, so `COPILOTKIT_API_KEY` is
-**required** (free tier). There are no URLs to set — the SDK defaults to the
+A Channel runs only through the Intelligence runtime, so `INTELLIGENCE_API_KEY` is
+**required** (free tier; `COPILOTKIT_API_KEY` is a deprecated alias, still read as a
+fallback). There are no URLs to set — the SDK defaults to the
managed Intelligence platform. The platform adapters stay direct — the runtime that owns the Channel starts each
of them for you. Linear and Notion are independent — set only the ones you want;
the agent wires up whichever credentials are present.
diff --git a/examples/slack/app/index.ts b/examples/slack/app/index.ts
index c0c6c13f6a..125eee815f 100644
--- a/examples/slack/app/index.ts
+++ b/examples/slack/app/index.ts
@@ -13,7 +13,7 @@
* rendering) is platform-agnostic and shared verbatim.
*
* RUN MODEL — a Channel runs ONLY through the Intelligence runtime, so this
- * example needs an Intelligence key (free tier: `COPILOTKIT_API_KEY`; the
+ * example needs an Intelligence key (free tier: `INTELLIGENCE_API_KEY`; the
* platform URLs default to the managed service). The platform adapters stay DIRECT (they keep their own
* Slack/Discord/Telegram/WhatsApp credentials + transports); the runtime OWNS
* the Channel's lifecycle and STARTS all of its direct adapters for us. So all
@@ -73,6 +73,34 @@ const required = (name: string): string => {
return v;
};
+/**
+ * Resolves the Intelligence project key.
+ *
+ * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and
+ * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a
+ * deprecated alias, still read so an existing `.env` keeps working.
+ */
+const requiredIntelligenceKey = (): string => {
+ const key =
+ process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY;
+ if (!key) {
+ console.error(
+ "Missing required env var: INTELLIGENCE_API_KEY\n" +
+ "Channels run only through the Intelligence runtime, which needs an " +
+ "Intelligence key (free tier).\n" +
+ " Run `copilotkit project select` to provision one, or set it manually.\n" +
+ "No URLs to set: the SDK defaults to the managed Intelligence platform.",
+ );
+ process.exit(1);
+ }
+ if (!process.env.INTELLIGENCE_API_KEY) {
+ console.warn(
+ "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.",
+ );
+ }
+ return key;
+};
+
/** True only when every named env var is set and non-empty. */
const have = (...names: string[]): boolean =>
names.every((n) => Boolean(process.env[n]));
@@ -283,7 +311,7 @@ async function main() {
const intelligence = new CopilotKitIntelligence({
apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL,
wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL,
- apiKey: required("COPILOTKIT_API_KEY"),
+ apiKey: requiredIntelligenceKey(),
});
// Declare the Channel on the Intelligence runtime, which OWNS its lifecycle:
diff --git a/examples/slack/app/managed.test.ts b/examples/slack/app/managed.test.ts
index 39180c030a..c84e92bf9c 100644
--- a/examples/slack/app/managed.test.ts
+++ b/examples/slack/app/managed.test.ts
@@ -66,6 +66,7 @@ const envKeys = [
"COPILOTKIT_INTELLIGENCE_URL",
"COPILOTKIT_INTELLIGENCE_WS_URL",
"COPILOTKIT_API_KEY",
+ "INTELLIGENCE_API_KEY",
] as const;
describe("managed channel entrypoint", () => {
@@ -89,7 +90,7 @@ describe("managed channel entrypoint", () => {
// plane is deployed separately, so there is no derive from apiUrl.
process.env.COPILOTKIT_INTELLIGENCE_URL = "http://localhost:4201";
process.env.COPILOTKIT_INTELLIGENCE_WS_URL = "ws://localhost:4401";
- process.env.COPILOTKIT_API_KEY = "cpk-test";
+ process.env.INTELLIGENCE_API_KEY = "cpk-test";
let sigterm: (() => void) | undefined;
vi.spyOn(process, "on").mockImplementation(((event, listener) => {
@@ -105,6 +106,12 @@ describe("managed channel entrypoint", () => {
await import("./managed.js");
await vi.waitFor(() => expect(sigterm).toBeTypeOf("function"));
+ // The canonical name reaches the client. A key that is merely present in
+ // the environment proves nothing; this proves it was consumed.
+ expect(fakes.CopilotKitIntelligence).toHaveBeenCalledWith(
+ expect.objectContaining({ apiKey: "cpk-test" }),
+ );
+
// The listener is created from the NORMAL runtime handler — no realtime
// gateway launcher is involved.
expect(fakes.createCopilotNodeListener).toHaveBeenCalledWith(
@@ -130,4 +137,31 @@ describe("managed channel entrypoint", () => {
// stop() threw, so shutdown exits nonzero.
expect(exit).toHaveBeenCalledWith(1);
});
+
+ it("still reads the deprecated COPILOTKIT_API_KEY alias", async () => {
+ for (const key of envKeys) previousEnv.set(key, process.env[key]);
+ vi.resetModules();
+ fakes.CopilotKitIntelligence.mockClear();
+ process.env.AGENT_URL = "http://agent.test/run";
+ delete process.env.INTELLIGENCE_API_KEY;
+ process.env.COPILOTKIT_API_KEY = "cpk-legacy";
+
+ vi.spyOn(process, "on").mockImplementation(
+ (() => process) as typeof process.on,
+ );
+ vi.spyOn(process, "exit").mockImplementation(
+ (() => undefined as never) as typeof process.exit,
+ );
+ vi.spyOn(console, "log").mockImplementation(() => {});
+ vi.spyOn(console, "warn").mockImplementation(() => {});
+ vi.spyOn(console, "error").mockImplementation(() => {});
+
+ await import("./managed.js");
+
+ await vi.waitFor(() =>
+ expect(fakes.CopilotKitIntelligence).toHaveBeenCalledWith(
+ expect.objectContaining({ apiKey: "cpk-legacy" }),
+ ),
+ );
+ });
});
diff --git a/examples/slack/app/managed.ts b/examples/slack/app/managed.ts
index ccfde3517e..ff53760e43 100644
--- a/examples/slack/app/managed.ts
+++ b/examples/slack/app/managed.ts
@@ -50,6 +50,34 @@ const required = (name: string): string => {
return v;
};
+/**
+ * Resolves the Intelligence project key.
+ *
+ * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and
+ * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a
+ * deprecated alias, still read so an existing `.env` keeps working.
+ */
+const requiredIntelligenceKey = (): string => {
+ const key =
+ process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY;
+ if (!key) {
+ console.error(
+ "Missing required env var: INTELLIGENCE_API_KEY\n" +
+ "Channels run only through the Intelligence runtime, which needs an " +
+ "Intelligence key (free tier).\n" +
+ " Run `copilotkit project select` to provision one, or set it manually.\n" +
+ "No URLs to set: the SDK defaults to the managed Intelligence platform.",
+ );
+ process.exit(1);
+ }
+ if (!process.env.INTELLIGENCE_API_KEY) {
+ console.warn(
+ "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.",
+ );
+ }
+ return key;
+};
+
/**
* The managed Channel `name` is chosen HERE, in code — it is the project-unique
* identifier the runtime uses to derive the managed Channel's activation config
@@ -132,7 +160,7 @@ async function main() {
const intelligence = new CopilotKitIntelligence({
apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL,
wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL,
- apiKey: required("COPILOTKIT_API_KEY"),
+ apiKey: requiredIntelligenceKey(),
});
const runtime = new CopilotRuntime({
diff --git a/examples/teams/.env.example b/examples/teams/.env.example
index 78410cd5ee..426727d1c1 100644
--- a/examples/teams/.env.example
+++ b/examples/teams/.env.example
@@ -9,7 +9,8 @@ OPENAI_API_KEY=
# starts/stops it is configured with an Intelligence key. Free tier is enough.
# Project runtime API key (cpk-…). The only Intelligence value you need: the SDK
# defaults both URLs to the managed platform.
-COPILOTKIT_API_KEY=cpk-...
+# `COPILOTKIT_API_KEY` is a deprecated alias for this variable, still read as a fallback.
+INTELLIGENCE_API_KEY=cpk-...
# Optional, for a self-hosted or dev Intelligence deployment only. Set BOTH or
# neither: the realtime plane is a DIFFERENT host from the API (api.… vs
# realtime.…), so it cannot be derived from the API URL, and setting one alone
diff --git a/examples/teams/README.md b/examples/teams/README.md
index a9a1274466..d8f5a6385c 100644
--- a/examples/teams/README.md
+++ b/examples/teams/README.md
@@ -23,7 +23,7 @@ From this directory (after `pnpm install` at the repo root):
```sh
export OPENAI_API_KEY=sk-... # or add it to .env (see .env.example)
-export COPILOTKIT_API_KEY=cpk-... # Intelligence key (free tier)
+export INTELLIGENCE_API_KEY=cpk-... # Intelligence key (free tier)
pnpm start # starts the bot on http://localhost:3978/api/messages
```
@@ -176,10 +176,11 @@ Set the environment for wherever you deploy:
- `OPENAI_API_KEY` _(required)_: the bot runs a `BuiltInAgent` and exits at
startup without it.
- `OPENAI_MODEL` _(optional)_: defaults to `openai/gpt-5.5`.
-- `COPILOTKIT_API_KEY` _(required)_: the Intelligence runtime that owns the
+- `INTELLIGENCE_API_KEY` _(required)_: the Intelligence runtime that owns the
Channel lifecycle. A Channel runs only through Intelligence, so the bot exits
at startup without it (free tier is enough). No URLs to configure — the SDK
- defaults to the managed Intelligence platform.
+ defaults to the managed Intelligence platform. `COPILOTKIT_API_KEY` is a
+ deprecated alias, still read as a fallback.
- `COPILOTKIT_INTELLIGENCE_URL` / `COPILOTKIT_INTELLIGENCE_WS_URL` _(optional)_:
point the bot at a self-hosted or dev Intelligence deployment. Set **both or
neither**: the API and realtime planes are **separate hosts**
diff --git a/examples/teams/app/index.tsx b/examples/teams/app/index.tsx
index db0f987143..a020d21210 100644
--- a/examples/teams/app/index.tsx
+++ b/examples/teams/app/index.tsx
@@ -16,7 +16,7 @@
* `.stop()`) — there is no `bot.start()`/`bot.stop()` and no standalone path.
*
* Requires `OPENAI_API_KEY` (the BuiltInAgent's LLM) AND an Intelligence key
- * (`COPILOTKIT_API_KEY` — free tier; the platform URLs default to the managed
+ * (`INTELLIGENCE_API_KEY` — free tier; the platform URLs default to the managed
* service), which the runtime that owns the Channel is configured with. No
* Microsoft credentials are needed to test in the M365 Agents Playground:
*
@@ -64,25 +64,32 @@ if (!process.env.OPENAI_API_KEY) {
process.exit(1);
}
-// A Channel runs ONLY through the Intelligence runtime — the runtime that owns
-// the Channel's lifecycle is configured with an Intelligence key. Fail fast
-// here rather than deep in activation. Free tier is enough for this demo.
-const required = (name: string): string => {
- const v = process.env[name];
- if (!v) {
+/**
+ * Resolves the Intelligence project key.
+ *
+ * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and
+ * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a
+ * deprecated alias, still read so an existing `.env` keeps working.
+ */
+const requiredIntelligenceKey = (): string => {
+ const key =
+ process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY;
+ if (!key) {
console.error(
- `Missing ${name}.\n` +
+ "Missing required env var: INTELLIGENCE_API_KEY\n" +
"Channels run only through the Intelligence runtime, which needs an " +
"Intelligence key (free tier).\n" +
- " export COPILOTKIT_API_KEY=cpk-... (or add it to examples/teams/.env)\n" +
- "No URLs to set: the SDK defaults to the managed Intelligence platform. A\n" +
- "self-hosted deployment exports COPILOTKIT_INTELLIGENCE_URL AND\n" +
- "COPILOTKIT_INTELLIGENCE_WS_URL — they are DIFFERENT hosts, so the websocket\n" +
- "URL cannot be derived from the API URL.",
+ " Run `copilotkit project select` to provision one, or set it manually.\n" +
+ "No URLs to set: the SDK defaults to the managed Intelligence platform.",
);
process.exit(1);
}
- return v;
+ if (!process.env.INTELLIGENCE_API_KEY) {
+ console.warn(
+ "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.",
+ );
+ }
+ return key;
};
const port = Number(process.env.PORT ?? 3978);
@@ -248,7 +255,7 @@ bot.onMessage(async ({ thread, message }) => {
const intelligence = new CopilotKitIntelligence({
apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL,
wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL,
- apiKey: required("COPILOTKIT_API_KEY"),
+ apiKey: requiredIntelligenceKey(),
});
// Declare the Channel on the Intelligence runtime. The runtime OWNS the
diff --git a/lefthook.yml b/lefthook.yml
index f2f48ee553..ff77f76a76 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -103,6 +103,16 @@ pre-commit:
fi
fi
+ check-intelligence-env-names:
+ tags: intelligence-env-names
+ # No glob: the retired names can reappear in any doc, README, example, or
+ # skill, so this runs on every commit rather than a path subset.
+ run: pnpm check:intelligence-env-names
+ fail_text: |
+ A non-canonical Intelligence env var name was found.
+ The canonical name is INTELLIGENCE_API_KEY.
+ See scripts/validate-intelligence-env-names.ts for the allowlist.
+
check-plugin-skills:
tags: plugin-skills
glob: "{packages/*/skills/**,skills/runtime/**,skills/react-core/**,skills/a2ui-renderer/**,scripts/sync-plugin-skills.ts,.claude-plugin/**,packages/runtime/package.json}"
diff --git a/package.json b/package.json
index a33c4d7fb6..ff14f50f52 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"attw": "nx run-many -t attw --projects=packages/**",
"check:packages": "nx run-many -t publint,attw --projects=packages/**",
"validate:model-names": "tsx scripts/validate-doc-model-names.ts",
+ "check:intelligence-env-names": "tsx scripts/validate-intelligence-env-names.ts",
"check:plugin-skills": "tsx scripts/sync-plugin-skills.ts --check",
"generate:channel-native-catalogs": "node scripts/channel-native-catalogs.mjs generate",
"check:channel-native-catalogs": "node scripts/channel-native-catalogs.mjs check",
diff --git a/packages/channels-core/README.md b/packages/channels-core/README.md
index 4c3a2bf025..796448fef4 100644
--- a/packages/channels-core/README.md
+++ b/packages/channels-core/README.md
@@ -87,7 +87,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [channel],
});
diff --git a/packages/channels-discord/README.md b/packages/channels-discord/README.md
index 44bbddf253..80d97d1d71 100644
--- a/packages/channels-discord/README.md
+++ b/packages/channels-discord/README.md
@@ -63,7 +63,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [bot],
});
diff --git a/packages/channels-slack/README.md b/packages/channels-slack/README.md
index da59dc3f4b..c97c56bc37 100644
--- a/packages/channels-slack/README.md
+++ b/packages/channels-slack/README.md
@@ -67,7 +67,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [bot],
});
diff --git a/packages/channels-teams/README.md b/packages/channels-teams/README.md
index c91fac17e8..1f2cbd31d1 100644
--- a/packages/channels-teams/README.md
+++ b/packages/channels-teams/README.md
@@ -59,7 +59,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [bot],
});
diff --git a/packages/channels-telegram/README.md b/packages/channels-telegram/README.md
index 6d879247af..f55ee65b34 100644
--- a/packages/channels-telegram/README.md
+++ b/packages/channels-telegram/README.md
@@ -74,7 +74,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [bot],
});
diff --git a/packages/channels-whatsapp/README.md b/packages/channels-whatsapp/README.md
index 59115cbf99..0256ce524c 100644
--- a/packages/channels-whatsapp/README.md
+++ b/packages/channels-whatsapp/README.md
@@ -58,7 +58,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [bot],
});
diff --git a/packages/channels/README.md b/packages/channels/README.md
index c09395099c..0d431772f6 100644
--- a/packages/channels/README.md
+++ b/packages/channels/README.md
@@ -57,7 +57,7 @@ const runtime = new CopilotRuntime({
intelligence: new CopilotKitIntelligence({
// apiUrl and wsUrl default to the managed Intelligence platform — override
// both together only for a self-hosted deployment.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available
+ apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available
}),
channels: [channel],
});
diff --git a/packages/react-core/skills/react-core/references/threads.md b/packages/react-core/skills/react-core/references/threads.md
index 4cbb59e8cf..ec2903676b 100644
--- a/packages/react-core/skills/react-core/references/threads.md
+++ b/packages/react-core/skills/react-core/references/threads.md
@@ -128,8 +128,7 @@ import {
const intelligence = new CopilotKitIntelligence({
// apiUrl / wsUrl default to the managed Intelligence platform — leave unset.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
- organizationId: process.env.COPILOTKIT_ORG_ID!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
});
const runtime = new CopilotIntelligenceRuntime({
diff --git a/packages/runtime/skills/runtime/references/intelligence-mode.md b/packages/runtime/skills/runtime/references/intelligence-mode.md
index 67aa941d3a..89fa074b6c 100644
--- a/packages/runtime/skills/runtime/references/intelligence-mode.md
+++ b/packages/runtime/skills/runtime/references/intelligence-mode.md
@@ -1,13 +1,17 @@
# CopilotKit Intelligence Mode
+Published version: https://docs.copilotkit.ai/premium/connect-your-runtime
+
Intelligence currently ships as a managed cloud service. The only supported `apiUrl` /
`wsUrl` today is the CopilotKit-managed cloud Intelligence instance — the `ɵ`-prefixed
runtime internals and REST/WebSocket contract that back Intelligence are still
-stabilizing and `organizationId` is reserved for future self-hosted deployments. If you
+stabilizing. `organizationId` is not part of `CopilotKitIntelligenceConfig` today — the key
+alone scopes the project — and is reserved for future self-hosted deployments. If you
need on-prem durable threads today, use SSE mode with a persistent runner
(`SqliteAgentRunner` or a custom one) instead.
-Obtain `apiKey` and `organizationId` from the CopilotKit Intelligence dashboard.
+Obtain `apiKey` from the CopilotKit Intelligence dashboard, or let `copilotkit project select`
+provision a project-scoped key into `.env` as `INTELLIGENCE_API_KEY`.
### URL format
@@ -54,8 +58,7 @@ import {
const intelligence = new CopilotKitIntelligence({
// apiUrl / wsUrl default to the managed Intelligence platform — leave them unset.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
- organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORG_ID!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
});
const runtime = new CopilotRuntime({
@@ -191,28 +194,24 @@ new CopilotKitIntelligence({
// Scheme-swapped from apiUrl — WRONG HOST. Serves no socket; hangs instead of erroring.
wsUrl: apiUrl.replace(/^http/, "ws"),
apiKey,
- organizationId,
});
new CopilotKitIntelligence({
apiUrl: "https://api.intelligence.copilotkit.ai/api", // double /api prefix
wsUrl: "wss://realtime.intelligence.copilotkit.ai/socket", // /socket is not a real path
apiKey,
- organizationId,
});
new CopilotKitIntelligence({
apiUrl: "https://internal.myco.com/intelligence", // self-hosting is not yet supported
wsUrl: "wss://internal.myco.com/intelligence",
apiKey,
- organizationId,
});
new CopilotKitIntelligence({
// Only one plane overridden — wsUrl silently stays on the managed host. Warns, then hangs.
apiUrl: "https://api.intelligence.copilotkit.ai",
apiKey,
- organizationId,
});
```
@@ -221,8 +220,7 @@ Correct:
```typescript
new CopilotKitIntelligence({
// No apiUrl / wsUrl — they default to the managed platform.
- apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
- organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORG_ID!,
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
});
// For on-prem durability without Intelligence: SSE mode + SqliteAgentRunner.
```
@@ -239,8 +237,9 @@ Three failure modes to avoid:
Passing `apiUrl: ".../api"` produces double-prefixed `/api/api/threads`; passing
`wsUrl: ".../socket"` produces a broken `.../socket/runner` upgrade path.
3. Self-hosting Intelligence is not yet supported. The `ɵ`-prefixed runtime internals
- and REST/WebSocket contract are still stabilizing. `organizationId` is reserved for
- future self-hosted instances. For on-prem durable threads today, use SSE mode +
+ and REST/WebSocket contract are still stabilizing. `organizationId` is not a constructor
+ option today; it is reserved for future self-hosted instances. For on-prem durable
+ threads today, use SSE mode +
`SqliteAgentRunner` (see `copilotkit/agent-runners`).
Source: `packages/runtime/src/v2/runtime/intelligence-platform/client.ts:41-46, 68-69,
diff --git a/packages/runtime/src/v2/runtime/intelligence-platform/client.ts b/packages/runtime/src/v2/runtime/intelligence-platform/client.ts
index 27ecc0f27e..5a78ba804a 100644
--- a/packages/runtime/src/v2/runtime/intelligence-platform/client.ts
+++ b/packages/runtime/src/v2/runtime/intelligence-platform/client.ts
@@ -462,7 +462,7 @@ interface ThreadEnvelope {
* import { CopilotKitIntelligence, CopilotRuntime } from "@copilotkit/runtime";
*
* const intelligence = new CopilotKitIntelligence({
- * apiKey: process.env.COPILOTKIT_API_KEY!,
+ * apiKey: process.env.INTELLIGENCE_API_KEY!,
* });
*
* const runtime = new CopilotRuntime({
@@ -478,7 +478,7 @@ interface ThreadEnvelope {
* const intelligence = new CopilotKitIntelligence({
* apiUrl: "https://intelligence.internal",
* wsUrl: "wss://realtime.intelligence.internal",
- * apiKey: process.env.COPILOTKIT_API_KEY!,
+ * apiKey: process.env.INTELLIGENCE_API_KEY!,
* });
* ```
*/
diff --git a/scripts/validate-intelligence-env-names.ts b/scripts/validate-intelligence-env-names.ts
new file mode 100644
index 0000000000..a46f6b7055
--- /dev/null
+++ b/scripts/validate-intelligence-env-names.ts
@@ -0,0 +1,151 @@
+import { execFileSync } from "node:child_process";
+import * as path from "node:path";
+
+/**
+ * Guards the single canonical name for the Intelligence project API key.
+ *
+ * `INTELLIGENCE_API_KEY` is what `copilotkit project select` provisions into
+ * `.env`. Two other names were live in CopilotKit's own documentation and each
+ * produced an undefined key for a reader who followed it with a CLI-provisioned
+ * project (OSS-881):
+ *
+ * - `COPILOTKIT_INTELLIGENCE_API_KEY` — Channels READMEs and packaged skills.
+ * Retired outright: nothing ever read it.
+ * - `COPILOTKIT_API_KEY` — the Slack/Teams examples and the client's own TSDoc.
+ * Still read as a deprecated alias by those two examples, so it is allowed
+ * only at the small set of sites that implement or document that fallback.
+ *
+ * This is a documentation-drift guard, not a runtime check. It fails on a
+ * retired name reappearing anywhere, and on the alias appearing outside its
+ * allowlist.
+ */
+
+const REPO_ROOT = path.resolve(__dirname, "..");
+
+/** Never valid anywhere. Nothing has ever read this name. */
+const RETIRED = [
+ "COPILOTKIT_INTELLIGENCE_API_KEY",
+ "COPILOTKIT_INTELLIGENCE_ORG_ID",
+];
+
+/**
+ * Deprecated but still read as a fallback. Permitted only where the fallback is
+ * implemented or explicitly described as deprecated.
+ */
+const ALIAS = "COPILOTKIT_API_KEY";
+
+/**
+ * Paths allowed to mention {@link ALIAS}.
+ *
+ * `NEXT_PUBLIC_COPILOTKIT_API_KEY` is a different value entirely — the legacy
+ * Copilot Cloud public key — so files carrying only that prefixed form are
+ * matched and skipped by prefix rather than listed here.
+ */
+const ALIAS_ALLOWLIST = [
+ "examples/slack/.env.example",
+ "examples/slack/README.md",
+ "examples/slack/app/index.ts",
+ "examples/slack/app/managed.ts",
+ "examples/slack/app/managed.test.ts",
+ "examples/teams/.env.example",
+ "examples/teams/README.md",
+ "examples/teams/app/index.tsx",
+ "scripts/validate-intelligence-env-names.ts",
+ "skills/copilotkit-setup/SKILL.md",
+ // The importer genuinely accepts both names; these lines document that.
+ "showcase/shell-docs/src/content/docs/integrations/adk/threads-import.mdx",
+ "showcase/shell-docs/src/content/docs/integrations/langgraph/threads-import.mdx",
+ "showcase/shell-docs/src/content/snippets/shared/cli/cli.mdx",
+ "showcase/shell-docs/src/content/snippets/shared/threads/threads-import.mdx",
+];
+
+interface Violation {
+ file: string;
+ line: number;
+ name: string;
+ reason: string;
+}
+
+/** Returns `git grep -n` hits for one literal, or `[]` when there are none. */
+function grepRepo(
+ literal: string,
+): { file: string; line: number; text: string }[] {
+ let out: string;
+ try {
+ out = execFileSync("git", ["grep", "-n", "--fixed-strings", literal], {
+ cwd: REPO_ROOT,
+ encoding: "utf-8",
+ });
+ } catch {
+ // git grep exits 1 when there are no matches.
+ return [];
+ }
+ return out
+ .split("\n")
+ .filter(Boolean)
+ .map((row) => {
+ const [file, line, ...rest] = row.split(":");
+ return { file: file!, line: Number(line), text: rest.join(":") };
+ });
+}
+
+/** Collects every naming violation in the repository. */
+export function findViolations(): Violation[] {
+ const violations: Violation[] = [];
+
+ for (const name of RETIRED) {
+ for (const hit of grepRepo(name)) {
+ if (hit.file === "scripts/validate-intelligence-env-names.ts") continue;
+ violations.push({
+ file: hit.file,
+ line: hit.line,
+ name,
+ reason: "retired name; use INTELLIGENCE_API_KEY",
+ });
+ }
+ }
+
+ for (const hit of grepRepo(ALIAS)) {
+ if (ALIAS_ALLOWLIST.includes(hit.file)) continue;
+ // A different credential that merely shares the suffix.
+ if (hit.text.includes(`NEXT_PUBLIC_${ALIAS}`)) continue;
+ violations.push({
+ file: hit.file,
+ line: hit.line,
+ name: ALIAS,
+ reason: "deprecated alias; use INTELLIGENCE_API_KEY",
+ });
+ }
+
+ return violations;
+}
+
+function main(): void {
+ const violations = findViolations();
+
+ if (violations.length === 0) {
+ console.log("Intelligence env var names are canonical.");
+ process.exit(0);
+ }
+
+ console.log(
+ `Found ${violations.length} non-canonical Intelligence env var reference${
+ violations.length === 1 ? "" : "s"
+ }:\n`,
+ );
+ for (const v of violations) {
+ console.log(` ${v.file}:${v.line} ${v.name} — ${v.reason}`);
+ }
+ console.log(
+ "\nThe canonical name is INTELLIGENCE_API_KEY — the name `copilotkit project select`\n" +
+ "provisions. If a site legitimately implements the deprecated alias fallback, add it\n" +
+ "to ALIAS_ALLOWLIST in scripts/validate-intelligence-env-names.ts.",
+ );
+ process.exit(1);
+}
+
+const isDirectRun = typeof require !== "undefined" && require.main === module;
+
+if (isDirectRun) {
+ main();
+}
diff --git a/showcase/shell-docs/src/content/docs/meta.json b/showcase/shell-docs/src/content/docs/meta.json
index 06bcd13f69..67952045e2 100644
--- a/showcase/shell-docs/src/content/docs/meta.json
+++ b/showcase/shell-docs/src/content/docs/meta.json
@@ -55,6 +55,7 @@
"---Intelligence Platform---",
"premium/overview",
"premium/managed-intelligence-platform",
+ "premium/connect-your-runtime",
"premium/self-hosting",
"premium/intelligence-platform",
"---Deploy---",
diff --git a/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx b/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx
new file mode 100644
index 0000000000..e24e2b505d
--- /dev/null
+++ b/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx
@@ -0,0 +1,111 @@
+---
+title: Connect your runtime to Intelligence
+description: "Wire an existing CopilotKit runtime to the Enterprise Intelligence Platform — construct CopilotKitIntelligence with a project API key, identify users, and confirm the credential is actually being used."
+icon: "lucide/PlugZap"
+doc_type: how-to
+premium: true
+---
+
+Connecting a runtime to Intelligence takes two things: construct a
+`CopilotKitIntelligence` client with your project API key, and pass it to your
+runtime as `intelligence`. The runtime reads the key from the client you pass,
+not from the environment.
+
+This page is that wiring step. For what the platform is and why you would use it,
+see the [Enterprise Intelligence Platform overview](/premium/overview) and the
+[architecture page](/premium/intelligence-platform).
+
+## Before you start
+
+You need a project API key. Either provision one with the CLI:
+
+```bash title="Terminal"
+npx copilotkit login
+npx copilotkit project select
+```
+
+`project select` writes a project-scoped key to `.env` as `INTELLIGENCE_API_KEY`.
+You can also copy a key from the
+[cloud-hosted dashboard](/premium/managed-intelligence-platform).
+
+```bash title=".env"
+INTELLIGENCE_API_KEY=cpk-...
+```
+
+
+ This is a server-side secret. Do not give it a `NEXT_PUBLIC_` or `VITE_`
+ prefix — that ships it to the browser.
+
+
+## Wire the runtime
+
+Construct the client once and pass it to `CopilotRuntime` as `intelligence`.
+
+```ts title="app/api/copilotkit/[[...slug]]/route.ts"
+import {
+ CopilotRuntime,
+ CopilotKitIntelligence,
+ createCopilotRuntimeHandler,
+} from "@copilotkit/runtime/v2";
+
+const intelligence = new CopilotKitIntelligence({
+ // apiUrl and wsUrl default to the managed platform — leave them unset.
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
+});
+
+const runtime = new CopilotRuntime({
+ agents,
+ intelligence,
+ // Threads are per-user. Without this every visitor shares one history.
+ identifyUser: (request) => ({
+ id: request.headers.get("x-user-id") ?? "anonymous",
+ }),
+});
+
+export const { GET, POST } = createCopilotRuntimeHandler({ runtime });
+```
+
+`apiKey` is the only required field. The key scopes the project, so there is no
+separate organization or project id to pass.
+
+## Confirm the credential is actually used
+
+A build that compiles and a chat that replies both prove nothing about
+Intelligence — a runtime in SSE mode does all of that with the key unread. So a
+green round trip in the browser is not evidence on its own.
+
+Confirm it from the product side instead. Open your project in the
+[cloud-hosted dashboard](/premium/managed-intelligence-platform) and send a
+message in your app. A thread should appear. If none does, the runtime never
+reached the platform and is running in SSE mode, whatever the browser showed.
+
+## Self-hosted deployments
+
+`apiUrl` and `wsUrl` default to the managed platform. Override them **together**
+or not at all — the API and realtime planes are deployed on different hosts, so
+the websocket URL cannot be derived from the API URL, and setting one alone
+leaves the other plane pointed at the managed host.
+
+```ts
+const intelligence = new CopilotKitIntelligence({
+ apiUrl: "https://api.intelligence.internal",
+ wsUrl: "wss://realtime.intelligence.internal",
+ apiKey: process.env.INTELLIGENCE_API_KEY!,
+});
+```
+
+Pass the bare websocket base: the client appends `/runner` and `/client` itself,
+and prepends `/api` to every REST call. Passing `apiUrl: ".../api"` produces
+double-prefixed `/api/api/threads`.
+
+See [Self-Hosting Enterprise Intelligence](/premium/self-hosting) for the full
+deployment path.
+
+## Troubleshooting
+
+| Symptom | Cause |
+| --- | --- |
+| Chat works, no threads in the dashboard | `intelligence` was never passed to `CopilotRuntime`; the runtime is in SSE mode. |
+| Opaque auth error on the first request | `INTELLIGENCE_API_KEY` is empty or belongs to a different project. |
+| Socket sits in `connecting`, then "did not settle in time" | `wsUrl` overridden alone, or pointed at the API host. |
+| `/api/api/...` in request logs | `apiUrl` included a `/api` suffix. |
diff --git a/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx b/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx
index cb66d2973e..9fce6a2b96 100644
--- a/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx
+++ b/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx
@@ -8,7 +8,7 @@ premium: true
The Enterprise Intelligence Platform is the platform backend behind production CopilotKit capabilities such as durable threads, realtime sync, project-scoped history, the hosted web app, and operational visibility. This page explains the mental model that applies to both [Cloud-Hosted Enterprise Intelligence](/premium/managed-intelligence-platform) and [Self-Hosting Enterprise Intelligence](/premium/self-hosting).
-For a product-level map of features and hosting options, start with the [Enterprise Intelligence Platform overview](/premium/overview). For Kubernetes installation, go straight to [Self-Hosting Enterprise Intelligence](/premium/self-hosting).
+For a product-level map of features and hosting options, start with the [Enterprise Intelligence Platform overview](/premium/overview). To wire an existing runtime to the platform, see [Connect your runtime to Intelligence](/premium/connect-your-runtime). For Kubernetes installation, go straight to [Self-Hosting Enterprise Intelligence](/premium/self-hosting).