---
title: "List a channel's routes"
method: GET
path: "/v1/projects/{project_id}/channels/{channel_id}/routes"
tags: ["Channels"]
---

# List a channel's routes

`GET /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`)

One channel's routes, newest first, `disabled` ones included — only `active` routes take part in resolution. For every route in the project in one read, use [`GET /v1/projects/{project_id}/channel-routes`](/docs/api/channel-routes/list-project-channel-routes).

## Query parameters

- `limit` integer
- `cursor` string

## Response `200`

A page of routes.

- ChannelRouteList
  - `data` ChannelRoute[], required
    - `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_id` string, nullable, required: The agent that answers, when `action` is `agent` or `oneshot`.
    - `text` string, nullable, required: The fixed text delivered, when `action` is `message`.
    - `repeat` union, required: How often a `message` fires: `every` (default), `once`, or `{ after_seconds }`. Null unless `action` is `message`.
      - 'every' | 'once'
      - object
        - `after_seconds` integer, required
    - `language` string, nullable, required: Preferred reply language; null lets the agent decide.
    - `config` object, 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.
    - `id` string, required
    - `project_id` string, required
    - `channel_id` string, required
    - `surface` string, nullable, required: The surface this route matches, or null for "any" (`GET /v1/channel-kinds`).
    - `match` object, required: The predicate bag, ANDed. `{}` matches every message on this surface.
    - `priority` integer, required: Tiebreak only, after specificity and `surface`.
    - `status` 'active' | 'disabled', required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `next_cursor` string, nullable, required

## Other responses

- `401`: Missing or invalid credentials.
- `403`: The credential is scoped to a different project, or the caller's role in the project does not carry this action.
- `404`: The resource does not exist (existence is not leaked).

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
