Runtime HTTP endpoints

The HTTP routes exposed by the CopilotKit runtime for self-hosting, proxying, and debugging.

Copy a prompt that guides your coding agent through CopilotKit setup.

When you mount the CopilotKit runtime with createCopilotExpressHandler, createCopilotHonoHandler, copilotRuntimeNextJSAppRouterEndpoint, or any of the other framework adapters, it serves a small set of HTTP routes under the basePath you choose, such as /api/copilotkit. Most applications never call these routes directly. The frontend proxy (ProxiedCopilotRuntimeAgent) calls them for you. When you self-host behind a reverse proxy, lock down auth, or debug a connection failure with curl, use this page to confirm what the runtime exposes.

Provider and handler pairs#

The browser provider and the Runtime handler have to agree on the transport. CopilotKit ships more than one name for each half, and they are not interchangeable: each provider setting requires a matching handler mode. Different pages and older apps show different pairs, so this table is the mapping:

Angular has no provider pairing to configure: provideCopilotKit discovers the transport from /info and exposes no useSingleEndpoint option. Only the handler half below applies to you.

Which handlers serve which mode#

ModeHandlers
Multi-route (default)createCopilotRuntimeHandler, createCopilotHonoHandler, createCopilotExpressHandler, createCopilotNodeHandler, createCopilotNodeListener
Single-routeAny of the above with mode: "single-route"
Single-route only, no optioncopilotRuntimeNextJSAppRouterEndpoint, copilotRuntimeNextJSPagesRouterEndpoint, copilotRuntimeNodeHttpEndpoint, copilotRuntimeNodeExpressEndpoint, copilotRuntimeNestEndpoint

The framework wrappers support Intelligence resources

Every copilotRuntime*Endpoint wrapper builds its handler with mode: "single-route". A current client and Runtime carry Rich Threads, Memory, and annotation requests through that endpoint. Setting useSingleEndpoint={false} still points the browser at REST routes that these wrappers do not serve.

Older code may use these deprecated aliases:

DeprecatedUse instead
createCopilotEndpointcreateCopilotHonoHandler
createCopilotEndpointSingleRoutecreateCopilotHonoHandler with mode: "single-route"
createCopilotEndpointExpresscreateCopilotExpressHandler
createCopilotEndpointSingleRouteExpresscreateCopilotExpressHandler with mode: "single-route"

A mismatched pair fails at discovery

A mismatch fails at discovery, not at your application code. A multi-route provider against a single-route Runtime 404s on GET {basePath}/info; a single-route provider against a multi-route Runtime posts an envelope the Runtime does not accept. See Connect route 404 on a fresh thread.

Multi-route mode (default)#

By default the runtime runs in multi-route mode, exposing a separate route per operation. Given a basePath of /api/copilotkit, the routes are:

Method & pathPurpose
GET /api/copilotkit/infoRuntime info. The frontend calls this on startup to discover registered agents and their metadata.
GET /api/copilotkit/inspector-metadataOptional trusted project, plan, license, action, usage, and expiry context for the Inspector. Intelligence-backed runtimes advertise this route with inspectorMetadata: true in the runtime-info response.
POST /api/copilotkit/agent/:agentId/runStart an agent run. The request body is an AG-UI RunAgentInput; the response is an SSE stream of AG-UI events.
POST /api/copilotkit/agent/:agentId/connectConnect to an agent's thread. Used to resume streaming after a reconnect or page refresh. Also an SSE stream.
POST /api/copilotkit/agent/:agentId/stop/:threadIdStop an in-progress run on a given thread.
POST /api/copilotkit/transcribeTranscribe audio (used by the voice / transcription input).

:agentId is the key under which you registered the agent in new CopilotRuntime({ agents: { ... } }), for example default or research-agent. :threadId is the thread the run belongs to.

The GET /info route is the same endpoint the frontend uses for agent discovery. If it isn't reachable from the runtime, the frontend reports a runtime_info_fetch_failed error. See Error Debugging.

An Intelligence runtime also returns runtimeEntitlements. ready includes the normalized feature and limit values. degraded, misconfigured, and unavailable include a structured error with a code, message, and retry flag. The /info request still succeeds when entitlement lookup fails so core Runtime behavior can continue; features that need a proven entitlement stay off. If Intelligence rejects the project key with a 401, the Runtime still returns 200 from /info. The body reports runtimeEntitlements.status as misconfigured with the non-retryable runtime_entitlements_misconfigured error code.

Inspector metadata#

An Intelligence-backed runtime adds inspectorMetadata: true to its runtime-info response. After the main connection completes, @copilotkit/core uses that flag to request GET {basePath}/inspector-metadata in the background. Older runtimes omit the flag, so newer clients skip the optional request.

A valid response is a versioned InspectorMetadataV1 JSON object. The response always uses Cache-Control: no-store, private. The route returns 204 with the same cache policy when data is absent, the schema is unsupported, the runtime is not backed by Intelligence, or the provider request fails. A metadata failure does not change the runtime connection or agent state. The upstream Intelligence request has a five-second deadline; a timeout follows the same private 204 path.

{
  "schemaVersion": 1,
  "identity": {
    "organizationName": "Acme",
    "projectName": "Support"
  },
  "plan": {
    "code": "team",
    "label": "Team"
  },
  "license": {
    "state": "valid"
  },
  "action": {
    "kind": "manage_plan",
    "url": "https://ops.example.com/account/organization/org_123/organization-billing"
  },
  "usage": {
    "used": 42,
    "limit": {
      "kind": "finite",
      "value": 1000
    },
    "expiringSoonCount": 7
  }
}

Every module is optional and independent. usage.expiringSoonCount is an additive V1 leaf for deadlines in the next 24 hours: 0 is a known count, while absence means no trusted expiry count is available. Shared removes a malformed expiry leaf without removing valid used, limit, or sibling modules. Older V1 producers may omit the leaf, and older consumers may ignore it without a synchronized deployment.

curl -i http://localhost:4000/api/copilotkit/inspector-metadata

The runtime uses its server-side Intelligence API key for the upstream request. It does not forward browser headers or cookies to Intelligence, and it does not expose provider error bodies to the browser. Auth headers and cookies can still protect the browser-to-runtime request like any other runtime route.

Probing the runtime with curl#

The fastest way to confirm a self-hosted runtime is wired up is to hit /info directly:

curl -s http://localhost:4000/api/copilotkit/info

You should get back a JSON body describing the registered agents. If you get a 404, your basePath doesn't match the URL you're requesting (or the handler isn't mounted). If you get a connection error, the server isn't listening on that host/port.

Enable Rich Threads routes#

If the Inspector says Finish setting up Rich Threads, your Intelligence license is active but the client did not find the features used to list and inspect saved Threads. Complete these steps so Runtime info advertises those features and the Inspector can load saved history.

Use this pre-built prompt to finish Intelligence setup faster.

Choose a Runtime route mode#

Single-route and multi-route handlers both expose Rich Threads. Use single-route when your server should expose only one POST endpoint:

const handler = createCopilotRuntimeHandler({
  runtime,
  basePath: "/api/copilotkit",
  mode: "single-route",
});

For multi-route, omit mode. Your server must mount the full Runtime subtree and allow GET, POST, PATCH, and DELETE.

Keep provideCopilotKit pointed at the Runtime base URL. The Angular SDK discovers the transport from Runtime info; it has no useSingleEndpoint option to remove.

Construct the Intelligence client#

intelligence is a CopilotKitIntelligence instance. Build it from your project's Intelligence API key:

import { CopilotKitIntelligence } from "@copilotkit/runtime/v2";

const intelligence = new CopilotKitIntelligence({
  apiKey: process.env.CPK_INTELLIGENCE_API_KEY!,
});

apiKey is the only required option. copilotkit project select writes this key into your project's .env as CPK_INTELLIGENCE_API_KEY, and this is what consumes it.

Keep the key server-side. It is a project API key, so it is a different credential from the browser-visible NEXT_PUBLIC_COPILOTKIT_LICENSE_KEY used by the Inspector — do not substitute one for the other.

apiUrl and wsUrl default to CopilotKit's managed platform. The API and realtime planes are deployed to different hosts, so one cannot be derived from the other by swapping the scheme. Override them only for a self-hosted or non-production deployment, and override both together — setting one alone points the two planes at different deployments, which logs a warning:

const intelligence = new CopilotKitIntelligence({
  apiKey: process.env.CPK_INTELLIGENCE_API_KEY!,
  apiUrl: process.env.INTELLIGENCE_API_URL,
  wsUrl: process.env.INTELLIGENCE_GATEWAY_WS_URL,
});

Identify the signed-in application user#

An Intelligence-backed web Runtime exposes Threads only when it can scope them to an application user. Add identifyUser and resolve the user from a server-verified session or token:

const runtime = new CopilotRuntime({
  agents,
  intelligence,
  identifyUser: async (request) => {
    const user = await authenticateApplicationUser(request);
    if (!user) throw new Error("Unauthorized");
    return { id: user.id, name: user.name };
  },
});

See Scope Rich Threads to the signed-in user for the full identity and authorization pattern.

Mount the Runtime handler#

For single-route, mount one exact path and allow POST:

app/api/copilotkit/route.ts
export { handler as POST };

For multi-route, pass the full basePath subtree to the Runtime. In file-based routers, use a catch-all or splat route. Allow GET, POST, PATCH, and DELETE:

app/api/copilotkit/[[...slug]]/route.ts
export { handler as GET, handler as POST, handler as PATCH, handler as DELETE };

See Deploy to any runtime for complete adapter examples.

Verify the advertised capabilities#

Restart the Runtime. For multi-route, request its info endpoint:

curl -s http://localhost:4000/api/copilotkit/info

An Intelligence-backed web Runtime that is ready for Rich Threads includes:

{
  "threadEndpoints": {
    "list": true,
    "inspect": true,
    "mutations": true,
    "realtimeMetadata": true
  }
}

For single-route, post an info envelope:

curl -s http://localhost:4000/api/copilotkit \
  -H 'content-type: application/json' \
  -d '{"method":"info"}'

The response advertises the resource bridge and its thread features:

{
  "singleRoute": {
    "resourceOperations": true,
    "threadEndpoints": {
      "list": true,
      "inspect": true,
      "mutations": true,
      "realtimeMetadata": true
    }
  }
}

Reload your app after this response is available. The Inspector will replace the setup state with the saved Threads list. Managed and self-hosted Intelligence use the same Runtime route setup.

Single-route mode#

If you prefer to expose a single POST endpoint, for example to simplify a reverse-proxy rule or an API gateway, pass mode: "single-route". In that mode the runtime exposes one POST {basePath} endpoint that accepts a JSON envelope { method, params, body } and dispatches internally to the same handlers:

app/api/copilotkit/route.ts (Express)
import { CopilotRuntime, BuiltInAgent } from "@copilotkit/runtime/v2";
import { createCopilotExpressHandler } from "@copilotkit/runtime/v2/express";

const runtime = new CopilotRuntime({
  agents: { default: new BuiltInAgent({ model: "openai/gpt-4o-mini" }) },
});

app.use(
  createCopilotExpressHandler({
    runtime,
    basePath: "/api/copilotkit",
    mode: "single-route",
  }),
);

The optional Inspector metadata operation uses the same endpoint with this envelope:

{ "method": "inspector/metadata" }

Its response and failure rules match GET {basePath}/inspector-metadata.

The client sends thread, memory, and annotation operations through the same endpoint with a resource/request envelope. The Runtime accepts only known resource paths and routes them through the same handlers as multi-route mode.

The Angular SDK negotiates the transport from the Runtime info response. Point provideCopilotKit at the base endpoint; no second route configuration is needed:

src/app/app.config.ts
provideCopilotKit({
  runtimeUrl: "/api/copilotkit",
});

Runtime info must remain reachable through the selected transport so the Angular client can discover the transport and registered agents.

CORS#

The Express and Hono adapters apply permissive CORS by default (origin: "*", all standard methods, all headers) so local development works out of the box. Pass cors: false to disable the built-in middleware and handle CORS yourself, or pass a configuration object to scope it for production:

createCopilotExpressHandler({
  runtime,
  basePath: "/api/copilotkit",
  cors: {
    origin: "https://app.example.com",
    methods: ["GET", "POST", "OPTIONS"],
  },
});

Authenticating requests#

Because these routes run on your server, they're the right place to enforce auth. The adapters accept lifecycle hooks. An onRequest hook runs before every request and can reject the request by throwing a Response:

createCopilotExpressHandler({
  runtime,
  basePath: "/api/copilotkit",
  hooks: {
    onRequest: ({ request }) => {
      if (!request.headers.get("authorization")) {
        throw new Response("Unauthorized", { status: 401 });
      }
    },
  },
});

See Auth for the full authentication guide.

For Inspector metadata, Core sends these current browser-to-runtime headers and fetch credentials on the optional request. The Runtime then starts a separate server-to-Intelligence request with only its configured Intelligence API key.

Connect route 404 on a fresh thread#

A frequent self-hosting symptom is a 404 from the POST /agent/:agentId/connect route right after the page loads, before the user has sent a single message. This usually means one of two things:

  1. The agentId in the URL isn't registered. The runtime returns {"error":"Agent not found","message":"Agent '<id>' does not exist"} with a 404 when no agent matches. The prebuilt components default to the agent named "default", so register one under that key (or pass an explicit agentId).
  2. connect() is called before any run() for an auto-minted thread. Some persistence backends only know about a thread once a run has produced events. See the AgentRunner guide and the /connect 404 troubleshooting entry.