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
- URL Structure: CCX captures route prefixes via
/:routePrefix/...patterns defined inbackend-go/main.go, line 275. - Configuration: Set the
RoutePrefixfield in theUpstreamstruct (backend-go/internal/config/config.go, line 69) withinconfig.json. - Filtering: The scheduler filters channels by matching
c.Param("routePrefix")againstUpstream.RoutePrefixinbackend-go/internal/scheduler/channel_scheduler.go, lines 417-424. - Dynamic Updates: Modify prefixes at runtime using
PATCH /api/channels/{id}, with logic handled inbackend-go/internal/config/config_messages.go, line 246. - Isolation: Empty prefix strings match default channels; non-empty prefixes create isolated routing scopes for multi-tenant or canary scenarios.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →