How to Configure Route Prefixes for Channel Routing in CCX

CCX isolates traffic between channels by capturing the first URL path segment as :routePrefix and matching it against each channel's configured RoutePrefix field, enabling multi-tenant deployments on a single backend instance.

CCX (Channels Configuration eXchange) supports logical channel isolation through configurable route prefixes. By inserting a custom prefix into API paths—such as /kimi/v1/chat/completions versus /openai/v1/chat/completions—a single CCX instance can serve multiple independent channels without code changes. This guide explains how to configure these prefixes in the BenedictKing/ccx repository, referencing the actual Go source implementation.

How Route Prefixes Work in CCX

CCX leverages Gin's URL parameter capture to extract route prefixes from incoming requests. When a client calls /:routePrefix/v1/chat/completions, the backend extracts the prefix and uses it to filter available upstream channels.

Route Definition in main.go

In backend-go/main.go at line 275, all public API routes use the :routePrefix placeholder:

router.GET("/:routePrefix/health", healthHandler)
router.POST("/:routePrefix/v1/messages", messagesHandler)
router.POST("/:routePrefix/v1/chat/completions", chatCompletionsHandler)

When a request matches /kimi/v1/messages, Gin populates c.Param("routePrefix") with the string "kimi" and passes this value downstream to handler functions.

Configuring Route Prefixes in config.json

Each channel's route prefix is defined in the Upstream struct within your config.json file.

The Upstream Struct Definition

In backend-go/internal/config/config.go at line 69, the Upstream struct declares the RoutePrefix field:

type Upstream struct {
    Name        string `json:"name"`
    Type        string `json:"type"`
    Key         string `json:"key"`
    BaseURL     string `json:"baseURL"`
    RoutePrefix string `json:"routePrefix,omitempty"` // e.g., "kimi"
}

Example Channel Configuration

Configure separate prefixes for different providers in your config.json:

{
  "channels": [
    {
      "name": "OpenAI-GPT4",
      "type": "chat",
      "key": "sk-openai-key",
      "baseURL": "https://api.openai.com",
      "routePrefix": "openai"
    },
    {
      "name": "Kimi-Moonshot",
      "type": "chat",
      "key": "sk-kimi-key",
      "baseURL": "https://api.moonshot.cn",
      "routePrefix": "kimi"
    }
  ]
}

With this configuration, requests to /openai/v1/chat/completions route to the OpenAI channel, while /kimi/v1/chat/completions route to the Kimi channel.

Scheduler-Level Prefix Filtering

The channel scheduler enforces route prefix isolation by filtering upstream channels based on the extracted URL parameter.

SelectChannel Implementation

In backend-go/internal/scheduler/channel_scheduler.go at lines 417-424, the SelectChannel function performs the prefix matching:

if routePrefix != "" {
    if upstream != nil && upstream.RoutePrefix == routePrefix {
        // Channel matches the requested prefix
        eligibleChannels = append(eligibleChannels, upstream)
    }
}

If the request URL contains /openai/..., the scheduler only selects channels where Upstream.RoutePrefix == "openai". Requests without a prefix (empty string) match only channels with RoutePrefix == "" (default channels).

Handler Integration

Business handlers extract the prefix and pass it to the scheduler. In backend-go/internal/handlers/responses/compact.go at line 180, the handler retrieves the prefix via c.Param("routePrefix"):

selection, err := channelScheduler.SelectChannel(
    c.Request.Context(),
    userID,
    failedChannels,
    scheduler.ChannelKindResponses,
    requestModel,
    c.Param("routePrefix"),  // Extracted from URL
    c.GetHeader("X-Channel"),
)

Runtime Updates via REST API

You can modify route prefixes dynamically without restarting the CCX server.

Updating RoutePrefix via PATCH

CCX exposes a REST endpoint to update channel configuration. Send a PATCH request to /api/channels/{id} with the new routePrefix value:

curl -X PATCH "http://localhost:8080/api/channels/2" \
     -H "Content-Type: application/json" \
     -d '{"routePrefix":"staging"}'

Configuration Update Logic

In backend-go/internal/config/config_messages.go at line 246, the update handler writes the new prefix back to the upstream configuration:

if updates.RoutePrefix != nil {
    upstream.RoutePrefix = *updates.RoutePrefix
}

After the PATCH request succeeds, subsequent requests to /staging/v1/chat/completions will route to the updated channel.

Practical Use Cases for Route Prefixes

Route prefixes enable sophisticated deployment patterns beyond simple path customization.

Multi-Tenant Isolation: Assign unique prefixes to each customer (e.g., /acme-corp/, /globex/). Each tenant's requests route exclusively to their designated channels, preventing cross-tenant data leakage while sharing the same CCX backend.

Canary Deployments: Deploy a new model version under a prefix like /canary/. Gradually migrate traffic by updating client URLs to use the canary prefix, allowing instant rollback by reverting the path.

Provider Abstraction: Create semantic prefixes like /fast/ or /cheap/ that map to different provider pools without exposing backend provider names to clients.

Summary

Frequently Asked Questions

What happens if no route prefix is provided in the request?

If a request is sent to /v1/chat/completions without a leading prefix segment, c.Param("routePrefix") returns an empty string. The scheduler in channel_scheduler.go will only select channels where RoutePrefix == "" (default channels), ensuring that prefixed channels remain isolated from unprefixed traffic.

Can I change a channel's route prefix without restarting the CCX server?

Yes. Use the management API endpoint PATCH /api/channels/{id} with a JSON body containing {"routePrefix":"newPrefix"}. The change takes effect immediately for all new requests, as the configuration update logic writes directly to the in-memory Upstream struct.

How does CCX handle requests where no channels match the provided prefix?

If SelectChannel cannot find any upstream channels with a RoutePrefix matching the request URL parameter, it returns an error indicating no channels are available for that specific prefix. The client receives an HTTP error response, preventing accidental fallback to unintended channels.

Are route prefixes required for every channel configuration?

No. The RoutePrefix field is optional (marked omitempty in the struct tags). Channels without an explicit prefix act as default channels and only receive traffic when the request URL lacks a prefix segment or when explicitly matched by custom routing logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →