Channel Routes
Answer differently per surface and condition, without touching the channel default.
Overview
A route is (surface, conditions) → action — a row on one
channel. On every inbound message, after checking whether the
address has its own action, naturali loads the channel's
active routes, discards those whose surface disagrees and whose match
predicates do not all evaluate true, and answers with the most specific
survivor. No survivor falls back to the channel's default.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Channel Routes.
Data Model
ChannelRoute
| Field | Type | Description |
|---|---|---|
id | string | Public route ID. |
project_id | string | The owning project. |
channel_id | string | The channel this route belongs to. |
surface | string, nullable | The surface this route matches (see Channel Kinds), or null for "any". |
match | object | The predicate bag, ANDed. {} matches every message on this surface. |
priority | integer | Tiebreak only, after specificity and surface. |
action | string | agent, message, or silence. |
agent_id | string, nullable | The agent that answers, when action is agent. |
text | string, nullable | The fixed text delivered, when action is message. |
repeat | string or object, nullable | every (default), once, or { after_seconds } — how often a message fires. |
language | string, nullable | Preferred reply language; null lets the agent decide. |
config | object, nullable | Conversation config — media handling, persona overrides, the Discord allowlist. |
status | string | active or disabled. |
created_at | string (date-time) | |
updated_at | string (date-time) |
Key Concepts
Specificity, most to least
When more than one active route matches, the winner is picked in this order:
highest count of matched predicates, then a route with surface set beats one
with surface null, then highest priority, then oldest created_at. An
address's own action (see Addresses) beats every route
outright, and a route always beats the channel's default.
One cell, one active route
Two active routes may not claim the identical (surface, match) combination —
creating or updating one into a collision is a 409 route_conflict. A
disabled route never conflicts, so pausing one and creating its replacement
is always safe.
Surfaces and predicates come from the kind
surface and the keys match may use are declared per channel kind — see
Channel Kinds. An unknown surface is 400 unknown_surface;
an unknown or wrong-typed predicate is 400 unknown_predicate. Writing a route
for a surface whose transport mode is off (a Discord guild_thread route
before mention_threads is enabled) still succeeds, with unreachable_surface
in the response warnings — turning the mode on later never requires
rewriting the table.
Examples
Route Discord server threads in one guild to a public agent, leaving DMs on the channel default:
- CLI
- SDK
- curl
naturali create-channel-route \
--project-id proj_V1StGXR8Z5jdHi6B \
--channel-id chan_V1StGXR8Z5jdHi6B \
--surface guild_thread \
--match '{ "guild_id": "9988776655" }' \
--action agent \
--agent-id agent_public_V1StGXR8Z5jdHi6B
const { data: route } = await naturali.channels.createChannelRoute({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
channel_id: 'chan_V1StGXR8Z5jdHi6B',
},
body: {
surface: 'guild_thread',
match: { guild_id: '9988776655' },
action: 'agent',
agent_id: 'agent_public_V1StGXR8Z5jdHi6B',
},
});
curl -X POST https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/channels/chan_V1StGXR8Z5jdHi6B/routes \
-H "Authorization: Bearer $NATURALI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"surface": "guild_thread",
"match": { "guild_id": "9988776655" },
"action": "agent",
"agent_id": "agent_public_V1StGXR8Z5jdHi6B"
}'
Read every route across a project in one call:
- CLI
- SDK
- curl
naturali list-project-channel-routes --project-id proj_V1StGXR8Z5jdHi6B
const { data } = await naturali.channels.listProjectChannelRoutes({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
});
curl https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/channel-routes \
-H "Authorization: Bearer $NATURALI_API_KEY"