Skip to main content

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

FieldTypeDescription
idstringPublic route ID.
project_idstringThe owning project.
channel_idstringThe channel this route belongs to.
surfacestring, nullableThe surface this route matches (see Channel Kinds), or null for "any".
matchobjectThe predicate bag, ANDed. {} matches every message on this surface.
priorityintegerTiebreak only, after specificity and surface.
actionstringagent, message, or silence.
agent_idstring, nullableThe agent that answers, when action is agent.
textstring, nullableThe fixed text delivered, when action is message.
repeatstring or object, nullableevery (default), once, or { after_seconds } — how often a message fires.
languagestring, nullablePreferred reply language; null lets the agent decide.
configobject, nullableConversation config — media handling, persona overrides, the Discord allowlist.
statusstringactive or disabled.
created_atstring (date-time)
updated_atstring (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:

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

Read every route across a project in one call:

naturali list-project-channel-routes --project-id proj_V1StGXR8Z5jdHi6B