> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runflow.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP Routing

> Expose several endpoints from one agent using the path and method of the Agent API URL

Every deployed agent has a public URL on the Agent API:

```
https://executor.runflow.ai/agent/<agentId>?token=<agentToken>
```

That URL accepts **any sub-path and any HTTP method**. The platform still calls your single exported `main`, but it passes the method and path on `input.request`. Your agent becomes its own router: one deploy, several endpoints.

```bash theme={null}
curl "https://executor.runflow.ai/agent/<agentId>/orders/123" \
  -H "X-Runflow-Token: <agentToken>"

curl -X POST "https://executor.runflow.ai/agent/<agentId>/orders" \
  -H "X-Runflow-Token: <agentToken>" \
  -H "Content-Type: application/json" \
  -d '{"sku": "A-42", "qty": 2}'
```

## What the agent receives

```typescript theme={null}
input.request = {
  method: 'GET',                       // GET, POST, PUT, PATCH, DELETE...
  path: '/orders/123',                 // everything after /agent/<agentId>
  params: ['orders', '123'],           // path segments, empty ones dropped
  body: { /* ... */ },                 // parsed JSON, urlencoded form, or multipart fields
  headers: { 'X-Trace-Id': ['abc'] },  // canonical keys, values are arrays
  query: { status: ['open'] },         // values are arrays
  cookies: [ /* ... */ ],
};
```

## Routing example

```typescript theme={null}
import { Agent, AgentInput, openai } from '@runflow-ai/sdk';

const agent = new Agent({
  name: 'orders-assistant',
  model: openai('gpt-4o'),
  instructions: 'Help customers with their orders.',
});

export async function main(input: AgentInput) {
  const { method = 'POST', params = [], body } = input.request ?? {};
  const [resource, id] = params;

  if (resource === 'orders' && method === 'GET' && id) {
    return getOrder(id);
  }
  if (resource === 'orders' && method === 'POST') {
    return createOrder(body);
  }
  if (resource === 'health') {
    return { ok: true };
  }

  // No sub-path: the regular conversational agent
  return agent.process(input);
}
```

> [!TIP]
>
> Keep a default branch. Calls without a sub-path, and every invocation that does not come through the Agent API, arrive without `input.request`.

## Sync, async and streaming

`async` and `stream` are reserved words, but only as the **first** segment after the agent id. The mode segment is stripped before the path reaches your code, so one router serves all three modes:

| URL | Mode | `input.request.path` |
| - | - | - |
| `/agent/<id>/orders/123` | sync | `/orders/123` |
| `/agent/<id>/async/orders/123` | async | `/orders/123` |
| `/agent/<id>/stream/orders/123` | streaming | `/orders/123` |

Do not name one of your routes `async` or `stream`.

## Returning a raw body

By default the response is wrapped in the Runflow envelope (`{ metadata, ... }`). Add `?raw=true` to return exactly what `main` returned:

* strings go out as `text/plain`, without quotes
* objects, arrays, numbers and booleans go out as JSON
* execution metadata moves to response headers: `X-Runflow-Execution-Id`, `X-Runflow-Duration-Ms`, `X-Runflow-Agent-Name`, `X-Runflow-Agent-Version`, `X-Runflow-Timestamp`

Use it when the caller expects a specific body, such as a webhook verification handshake.

## Reading the request

* **The body is not spread into the input.** Only `body.message` is copied to `input.message`. Read every other field from `input.request.body`.
* **Headers and query values are arrays.** Use `input.request.query?.status?.[0]` and `input.request.headers?.['X-Trace-Id']?.[0]`. Header names arrive in canonical form (`X-Trace-Id`, not `x-trace-id`).
* **Read cookies from the `Cookie` header.** The `cookies` field is typed as a map, but the platform currently sends an array of objects with capitalized keys (`{ Name, Value, ... }`). `input.request.headers?.Cookie?.[0]` has the raw string.
* **File uploads** sent as `multipart/form-data` land on `input.attachments[]`. See [Multi-Modal](https://docs.runflow.ai/advanced/multi-modal.md).

## Authentication and threads

* Send the agent token in the `X-Runflow-Token` header instead of `?token=`. The query string is delivered to the agent as is, so a token sent as a query param shows up in `input.request.query.token`.
* Send `X-Runflow-Thread-Id` to pin the call to a specific memory thread. The value is scoped to your tenant.

## Where `input.request` is available

| Invocation | `input.request` |
| - | - |
| Agent API (`/agent/<id>/...`) | Yes |
| Webhook triggers (WhatsApp, Meta, generic) | No, the payload is in `input.metadata` |
| Scheduled jobs | No |
| `agents.invoke()` and other cross-agent calls | No |
| `rf test` local server | No |

To test a route locally, call `main` with a hand-built `request` object:

```typescript theme={null}
await main({
  message: '',
  request: { method: 'GET', path: '/orders/123', params: ['orders', '123'] },
});
```

## Next Steps

> **[Agents](https://docs.runflow.ai/core-concepts/agents.md)**
>
> Configure the agent behind the default route

> **[Core Types](https://docs.runflow.ai/api-reference/types/core-types.md#agent-types)**
>
> Full `AgentInput` reference


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.