# How to Configure Route Prefixes for Channel Routing in CCX

> Learn to configure route prefixes for channel routing in CCX. Isolate traffic and enable multi-tenancy on a single backend instance by matching URL path segments to channel route prefixes.

- Repository: [Benedict King/ccx](https://github.com/BenedictKing/ccx)
- Tags: how-to-guide
- Published: 2026-05-29

---

**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`](https://github.com/BenedictKing/ccx/blob/main/backend-go/main.go) at line 275, all public API routes use the `:routePrefix` placeholder:

```go
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`](https://github.com/BenedictKing/ccx/blob/main/config.json) file.

### The Upstream Struct Definition

In [`backend-go/internal/config/config.go`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/config/config.go) at line 69, the `Upstream` struct declares the `RoutePrefix` field:

```go
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`](https://github.com/BenedictKing/ccx/blob/main/config.json):

```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`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/scheduler/channel_scheduler.go) at lines 417-424, the `SelectChannel` function performs the prefix matching:

```go
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`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/handlers/responses/compact.go) at line 180, the handler retrieves the prefix via `c.Param("routePrefix")`:

```go
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:

```bash
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`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/config/config_messages.go) at line 246, the update handler writes the new prefix back to the upstream configuration:

```go
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 in [`backend-go/main.go`](https://github.com/BenedictKing/ccx/blob/main/backend-go/main.go), line 275.
- **Configuration**: Set the `RoutePrefix` field in the `Upstream` struct ([`backend-go/internal/config/config.go`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/config/config.go), line 69) within [`config.json`](https://github.com/BenedictKing/ccx/blob/main/config.json).
- **Filtering**: The scheduler filters channels by matching `c.Param("routePrefix")` against `Upstream.RoutePrefix` in [`backend-go/internal/scheduler/channel_scheduler.go`](https://github.com/BenedictKing/ccx/blob/main/backend-go/internal/scheduler/channel_scheduler.go), lines 417-424.
- **Dynamic Updates**: Modify prefixes at runtime using `PATCH /api/channels/{id}`, with logic handled in [`backend-go/internal/config/config_messages.go`](https://github.com/BenedictKing/ccx/blob/main/backend-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`](https://github.com/BenedictKing/ccx/blob/main/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.