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

# Express

> Expose agents, teams, and workflows as REST API endpoints

The `@agentium/transport` package provides `createAgentRouter()` to generate a fully-featured Express router with endpoints for all your agents, teams, and workflows.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install @agentium/transport express
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add @agentium/transport express
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add @agentium/transport express
    ```
  </Tab>
</Tabs>

## Quick Start

### Explicit wiring

Pass agents, teams, and workflows by name:

```typescript theme={null}
import express from "express";
import { Agent, openai } from "@agentium/core";
import { createAgentRouter } from "@agentium/transport";

const assistant = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  instructions: "You are a helpful assistant.",
});

const app = express();
app.use(express.json());

const router = createAgentRouter({
  agents: { assistant },
});

app.use("/api", router);
app.listen(3000);
```

### Auto-discovery (zero-wiring)

Agents, teams, and workflows auto-register into a global `registry` when instantiated. The transport layer reads from this registry dynamically — entities created *after* the server starts are immediately available.

```typescript theme={null}
import express from "express";
import { Agent, openai } from "@agentium/core";
import { createAgentRouter } from "@agentium/transport";

const app = express();
app.use(express.json());
app.use("/api", createAgentRouter());  // no agents/teams/workflows needed

// Agents created anywhere auto-register and become available
new Agent({ name: "assistant", model: openai("gpt-4o") });
new Agent({ name: "analyst", model: openai("gpt-4o-mini") });

app.listen(3000);
// POST /api/agents/assistant/run  ✓
// POST /api/agents/analyst/run    ✓
```

You can also pass a mixed array via `serve`:

```typescript theme={null}
const router = createAgentRouter({
  serve: [assistant, analyst, researchTeam, pipeline],
});
```

This creates the following endpoints:

| Method | Path                            | Description                                                               |
| ------ | ------------------------------- | ------------------------------------------------------------------------- |
| POST   | `/api/agents/:name/run`         | Run agent, return JSON response                                           |
| POST   | `/api/agents/:name/stream`      | Stream response via Server-Sent Events                                    |
| POST   | `/api/agents/:name/corrections` | Record a human correction of agent output (requires `memory.corrections`) |
| POST   | `/api/teams/:name/run`          | Run team                                                                  |
| POST   | `/api/teams/:name/stream`       | Stream team via SSE                                                       |
| POST   | `/api/workflows/:name/run`      | Run workflow                                                              |
| GET    | `/api/agents`                   | List registered agents with metadata                                      |
| GET    | `/api/teams`                    | List registered teams                                                     |
| GET    | `/api/workflows`                | List registered workflows                                                 |
| GET    | `/api/registry`                 | List all registered names                                                 |
| GET    | `/api/tools`                    | List available tools (when toolkits/toolLibrary configured)               |
| GET    | `/api/tools/:name`              | Get single tool detail                                                    |
| GET    | `/api/admin/mcp`                | List MCP servers (when `admin` enabled)                                   |
| POST   | `/api/admin/mcp`                | Add + connect an MCP server                                               |
| GET    | `/api/admin/toolkits`           | List toolkit catalog                                                      |

## RouterOptions

<ParamField body="agents" type="Record<string, Agent | ServableAgent>">
  Map of named agents to expose. Each agent gets `/agents/:name/run`, `/agents/:name/stream`, and `/agents/:name/corrections` endpoints. Accepts any [ServableAgent](/transport/external-agents) — not just the first-party `Agent` class.
</ParamField>

<ParamField body="teams" type="Record<string, Team>">
  Map of named teams. Each gets `/teams/:name/run` and `/teams/:name/stream` endpoints.
</ParamField>

<ParamField body="workflows" type="Record<string, Workflow>">
  Map of named workflows. Each gets `/workflows/:name/run` endpoint.
</ParamField>

<ParamField body="serve" type="Servable[]">
  Mixed array of Agent, Team, and Workflow instances. Automatically classified and registered. An alternative to passing `agents`, `teams`, and `workflows` separately.
</ParamField>

<ParamField body="registry" type="Registry | false">
  Controls live auto-discovery. Defaults to the global `registry` (all auto-registered entities are available). Pass a custom `Registry` instance, or `false` to disable auto-discovery and only serve explicitly passed entities.
</ParamField>

<ParamField body="middleware" type="RequestHandler[]">
  Express middleware applied to all routes (e.g., auth, rate limiting).
</ParamField>

<ParamField body="swagger" type="SwaggerOptions">
  Enable Swagger UI. See [Swagger docs](/transport/swagger).
</ParamField>

<ParamField body="fileUpload" type="boolean | FileUploadOptions">
  Enable multipart file upload for multi-modal input. See [File Upload docs](/transport/file-upload).
</ParamField>

<ParamField body="toolkits" type="Toolkit[]">
  Toolkit instances whose tools are exposed via `GET /tools`. Useful for UI tool discovery.
</ParamField>

<ParamField body="toolLibrary" type="Record<string, ToolDef>">
  Named tools exposed via `GET /tools`. Merged with toolkit tools (explicit entries take precedence).
</ParamField>

<ParamField body="admin" type="boolean | { mcpManager?: MCPManager; middleware?: any[] }">
  Enable admin routes under `/admin` for managing MCP servers and the toolkit catalog at runtime.
  Pass `true` to use defaults, or provide a shared `MCPManager` instance and authentication middleware. See [MCP Admin](/mcp/admin).

  In production, admin routes **require** authentication middleware — the server will throw an error at startup if none is provided.
</ParamField>

## Request Format

### JSON Request

```bash theme={null}
curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -d '{"input": "What is TypeScript?"}'
```

### With Session

```bash theme={null}
curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -d '{"input": "What was my last question?", "sessionId": "user-123"}'
```

### With API Key

```bash theme={null}
curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -H "x-openai-api-key: sk-your-key" \
  -d '{"input": "Hello"}'
```

## Response Format

### Run Response

```json theme={null}
{
  "text": "TypeScript is a typed superset of JavaScript...",
  "toolCalls": [],
  "usage": {
    "promptTokens": 25,
    "completionTokens": 150,
    "totalTokens": 175,
    "providerMetrics": {
      "prompt_tokens": 25,
      "completion_tokens": 150,
      "total_tokens": 175,
      "prompt_tokens_details": { "cached_tokens": 0 },
      "completion_tokens_details": { "reasoning_tokens": 0 }
    }
  },
  "timeToFirstTokenMs": 320,
  "runId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "agentName": "assistant",
  "sessionId": "",
  "userId": "",
  "model": "gpt-4o",
  "modelProvider": "openai",
  "status": "completed",
  "createdAt": 1772453994305,
  "responseId": "chatcmpl-abc123",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "What is TypeScript?" }
  ],
  "metrics": {
    "inputTokens": 25,
    "outputTokens": 150,
    "totalTokens": 175,
    "timeToFirstTokenMs": 320,
    "durationMs": 1234
  },
  "structured": null,
  "durationMs": 1234
}
```

### Structured Output Response

When an agent has `structuredOutput` configured, the `structured` field contains the parsed and validated JSON object:

```json theme={null}
{
  "text": "{\"summary\":\"AI agents have advanced...\",\"keyPoints\":[...],\"confidence\":0.85}",
  "toolCalls": [],
  "usage": {
    "promptTokens": 82,
    "completionTokens": 129,
    "totalTokens": 211
  },
  "structured": {
    "summary": "AI agents have advanced significantly in 2026...",
    "keyPoints": [
      "Diagnostic accuracy improved by 20% with AI imaging tools.",
      "Customer service bots handle 70% of inquiries."
    ],
    "confidence": 0.85
  },
  "durationMs": 3026
}
```

### Stream Response (SSE)

```bash theme={null}
curl -X POST http://localhost:3000/api/agents/assistant/stream \
  -H "Content-Type: application/json" \
  -d '{"input": "Tell me a joke"}'
```

```
data: {"type":"text","text":"Why"}
data: {"type":"text","text":" did"}
data: {"type":"text","text":" the"}
...
data: {"type":"finish","finishReason":"stop"}
```

## List Endpoints

When auto-discovery is enabled (default), the router exposes list endpoints with rich metadata:

```bash theme={null}
# List all agents with model, provider, tools
curl http://localhost:3000/api/agents
```

```json theme={null}
[
  {
    "name": "assistant",
    "model": "gpt-4o",
    "provider": "openai",
    "tools": ["calculate"],
    "hasStructuredOutput": false
  }
]
```

```bash theme={null}
# List all registered names
curl http://localhost:3000/api/registry
```

```json theme={null}
{
  "agents": ["assistant", "analyst"],
  "teams": ["research"],
  "workflows": ["pipeline"]
}
```

***

## Full Example with Multiple Agents

```typescript theme={null}
import express from "express";
import { Agent, openai, google, defineTool } from "@agentium/core";
import { createAgentRouter } from "@agentium/transport";
import { z } from "zod";

const calculator = defineTool({
  name: "calculate",
  description: "Evaluate a math expression",
  parameters: z.object({ expression: z.string() }),
  execute: async ({ expression }) => String(eval(expression)),
});

// Agents auto-register into the global registry
const assistant = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  instructions: "You are a helpful assistant.",
  tools: [calculator],
});

const analyst = new Agent({
  name: "analyst",
  model: openai("gpt-4o-mini"),
  instructions: "You analyze data and provide insights.",
  structuredOutput: z.object({
    summary: z.string(),
    sentiment: z.enum(["positive", "negative", "neutral"]),
    confidence: z.number(),
  }),
});

const vision = new Agent({
  name: "vision",
  model: google("gemini-2.5-flash"),
  instructions: "You analyze images and describe what you see.",
});

const app = express();
app.use(express.json());

// Auto-discovery: no need to pass agents explicitly
const router = createAgentRouter({
  swagger: {
    enabled: true,
    title: "My AI API",
    description: "Multi-agent API powered by Agentium",
  },
  fileUpload: true,
});

app.use("/api", router);

app.listen(3000, () => {
  console.log("API running on http://localhost:3000");
  console.log("Swagger UI at http://localhost:3000/api/docs");
});
```

## Adding Custom Middleware

```typescript theme={null}
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  next();
};

const router = createAgentRouter({
  agents: { assistant },
  middleware: [authMiddleware],
});
```

## Error Handling

```typescript theme={null}
import { errorHandler } from "@agentium/transport";

app.use("/api", router);
app.use(errorHandler);
```

The `errorHandler` middleware catches errors and returns structured JSON responses:

```json theme={null}
{
  "error": "Agent \"unknown\" not found"
}
```

<Info>
  In production (`NODE_ENV=production`), 5xx errors return a generic `"Internal server error"` message. Stack traces and internal details are never exposed to clients.
</Info>

***

## Security

Agentium includes several built-in security measures for the Express transport layer:

### Rate Limiting

`createAgentRouter()` includes a built-in IP-based rate limiter. The rate limiter's internal state is periodically cleaned (every 60 seconds) to prevent unbounded memory growth from large numbers of unique IPs.

### Admin Route Authentication

In production (`NODE_ENV=production`), mounting admin routes without authentication middleware will throw an error at startup — not just a warning. Always pass auth middleware when enabling admin routes:

```typescript theme={null}
const router = createAgentRouter({
  admin: {
    middleware: [authMiddleware],
  },
  middleware: [authMiddleware],
});
```

### Request Logging

The `requestLogger` middleware sanitizes URL paths to prevent log injection. Control characters and newlines are stripped before logging.

### JSON Body Limits

The A2A server applies a 1MB JSON body size limit to prevent large-payload DoS attacks.

See the [Security](/security) page for a full overview of all security protections.
