Channels

Create a route

action is required; agent_id is required when it is agent, text when it is message; repeat is only valid alongside action: message. surface, when present, must be one this channel's kind serves (GET /v1/channel-kinds) — otherwise 400 unknown_surface. match keys must be predicates that kind's registry declares — otherwise 400 unknown_predicate. Writing a route for a surface whose transport mode is off (e.g. a Discord guild_thread route before mention_threads is enabled) still succeeds, with unreachable_surface in the response warnings.

post/v1/projects/{project_id}/channels/{channel_id}/routes

Request

  • Base URL: https://api.naturali.ai
  • URL: https://api.naturali.ai/v1/projects/{project_id}/channels/{channel_id}/routes
  • Auth: one of:
    • HTTP bearer
    • OAuth 2 (authorization code, authorization URL https://api.naturali.ai/authorize, token URL https://api.naturali.ai/token, scopes: mcp:access)

Request body

surfacestring nullable
matchobject
priorityinteger
action'agent' | 'oneshot' | 'message' | 'silence' required

What an inbound message becomes.

agent is a dialogue: the identity behind the message gets a session, and every later message on it continues the same one. oneshot runs the agent once and keeps nothing — no address, no actor, no session, no conversation — so each message is answered on its own. Choose it for work that is filed rather than discussed; choose agent when the answer depends on what came before.

message delivers fixed text without running an agent, and silence answers nothing.

agent_idstring
textstring
languagestring
configobject nullable

Conversation config bag — media handling, persona overrides, the Discord allowlist, and how a oneshot answers.

discord.reply picks the delivery: react (the default) adds discord.reaction — \u2705 unless named — to the message that triggered the run, mention answers beside it addressing whoever asked, react_or_mention reacts when the agent writes nothing and answers like mention when it writes a line, thread opens a thread and answers inside it, and none delivers nothing at all. A run that fails delivers nothing in every mode, so a reaction always means the work was done; there is no failure marker.

A mention with nothing else in it is ignored unless the action says otherwise, for any action. discord.use_replied_message: true takes the text of the message it replies to, so replying to a message with only the mention files that message. discord.empty_mention_text answers any other bare mention with that fixed text, addressing the sender, without running the agent.

discord.forward_user_id: true hands the sender's Discord user id to the agent's tools as the discord_user_id tool context, which a tool's headers read as {{context:discord_user_id}}. It is the id Discord reported for the message's author, never text the model wrote, so a tool can act for whoever sent it. Off unless set, because tool context reaches every tool the agent has.

Read only for oneshot. A conversational agent in a guild is its thread — that is what its session is keyed on — so it always opens one.

status'active' | 'disabled'

Example request

{
  "surface": "guild_thread",
  "match": {
    "guild_id": "9988776655"
  },
  "action": "agent",
  "agent_id": "agent_V1StGXR8Z5jdHi6B",
  "text": "Subscribe at https://example.com/pricing",
  "language": "en-US"
}

Response

Route created.

action'agent' | 'oneshot' | 'message' | 'silence' | 'null' nullable required

What an inbound message becomes; null only on an address with no action of its own, which the route table then decides.

agent is a dialogue: the identity behind the message gets a session, and every later message on it continues the same one. oneshot runs the agent once and keeps nothing — no address, no actor, no session, no conversation — so each message is answered on its own. Choose it for work that is filed rather than discussed; choose agent when the answer depends on what came before.

message delivers fixed text without running an agent, and silence answers nothing.

agent_idstring nullable required

The agent that answers, when action is agent or oneshot.

textstring nullable required

The fixed text delivered, when action is message.

languagestring nullable required

Preferred reply language; null lets the agent decide.

configobject nullable required

Conversation config bag — media handling, persona overrides, the Discord allowlist, and how a oneshot answers.

discord.reply picks the delivery: react (the default) adds discord.reaction — \u2705 unless named — to the message that triggered the run, mention answers beside it addressing whoever asked, react_or_mention reacts when the agent writes nothing and answers like mention when it writes a line, thread opens a thread and answers inside it, and none delivers nothing at all. A run that fails delivers nothing in every mode, so a reaction always means the work was done; there is no failure marker.

A mention with nothing else in it is ignored unless the action says otherwise, for any action. discord.use_replied_message: true takes the text of the message it replies to, so replying to a message with only the mention files that message. discord.empty_mention_text answers any other bare mention with that fixed text, addressing the sender, without running the agent.

discord.forward_user_id: true hands the sender's Discord user id to the agent's tools as the discord_user_id tool context, which a tool's headers read as {{context:discord_user_id}}. It is the id Discord reported for the message's author, never text the model wrote, so a tool can act for whoever sent it. Off unless set, because tool context reaches every tool the agent has.

Read only for oneshot. A conversational agent in a guild is its thread — that is what its session is keyed on — so it always opens one.

idstring required
project_idstring required
channel_idstring required
surfacestring nullable required

The surface this route matches, or null for "any" (GET /v1/channel-kinds).

matchobject required

The predicate bag, ANDed. {} matches every message on this surface.

priorityinteger required

Tiebreak only, after specificity and surface.

status'active' | 'disabled' required
created_atstring date-time required
updated_atstring date-time required

Example response

{
  "action": "agent",
  "agent_id": "agent_V1StGXR8Z5jdHi6B",
  "text": "Subscribe at https://example.com/pricing",
  "language": "en-US",
  "config": {
    "media": {
      "audio": "transcribe"
    },
    "discord": {
      "allowed_role_ids": [
        "1290000000000000042"
      ]
    }
  },
  "id": "route_V1StGXR8Z5jdHi6B",
  "project_id": "proj_V1StGXR8Z5jdHi6B",
  "channel_id": "chan_V1StGXR8Z5jdHi6B",
  "surface": "guild_thread",
  "match": {
    "guild_id": "9988776655"
  },
  "status": "active"
}

Changes

No recorded changes to this operation across all 1 revision of this API.