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

# Run Agents

> Discover lanes, start agent runs, and poll results.

Agent runs are asynchronous. Start a run, then poll the run until `status` is terminal.

## Discover lanes

```http theme={null}
GET /v1/public/agents/{agentId}
Authorization: Bearer ers_live_...
```

The response returns the public lanes that can be started through the API.

```json theme={null}
{
  "agent": {
    "id": "clx_agent_123",
    "name": "OCR Quote Intake",
    "description": "Extract quote data from emails and attachments.",
    "behaviorType": "pipeline",
    "updatedAt": "2026-05-26T00:00:00.000Z"
  },
  "lanes": [
    {
      "id": "start_ocr_quote_intake",
      "name": "OCR Quote Intake",
      "mode": "structured",
      "inputSchema": {
        "type": "object",
        "additionalProperties": true,
        "required": ["sourceRecordType", "sourceRecordId"],
        "properties": {
          "sourceRecordType": { "type": "string" },
          "sourceRecordId": { "type": "string" },
          "emailBody": { "type": "string" }
        }
      },
      "attachments": {
        "supported": true,
        "required": false,
        "fields": [],
        "acceptedTypes": [],
        "maxRequestBytes": 26214400
      }
    }
  ],
  "defaultLaneId": "start_ocr_quote_intake"
}
```

The lane `id` is the same lane ID used in the Erstan frontend. If `defaultLaneId` is present, the agent has one public lane and callers may omit `laneId` when starting a run.

## Start a run

```http theme={null}
POST /v1/public/agents/{agentId}/runs
Authorization: Bearer ers_live_...
Content-Type: application/json
```

`agentId` is the agent workflow ID returned by `GET /v1/public/agents`.

## Request body

| Field            | Type   | Required                       | Notes                                                                                    |
| ---------------- | ------ | ------------------------------ | ---------------------------------------------------------------------------------------- |
| `laneId`         | string | Required for multi-lane agents | Omit only when `defaultLaneId` is present.                                               |
| `input`          | object | Yes                            | Payload validated against the selected lane's `inputSchema`.                             |
| `attachments`    | array  | No                             | Files for the lane. Each item needs `name`, `type`, and either `base64` or `storageKey`. |
| `metadata`       | object | No                             | Caller metadata stored with the run. Useful for source record IDs.                       |
| `idempotencyKey` | string | No                             | Up to 200 characters. Reuse on retries to avoid duplicate runs.                          |

<Warning>
  `input` is business data for the selected lane, not a way to reconfigure the saved agent. Top-level reserved runtime-control fields are rejected with `reserved_input_field` and no run is started. These include tool bindings plus tool and skill policies (`tools`, `toolPolicy`, `skillPolicy`, `skillIds`), write/approval controls such as `writePolicy` and `autoApproveWrites`, model/provider selectors, and workflow or context selectors. Configure those controls in the saved agent definition instead.
</Warning>

## Lane modes

| Mode         | Input shape                                                                    |
| ------------ | ------------------------------------------------------------------------------ |
| `structured` | `input` must match the lane `inputSchema`. This is used for form/intake lanes. |
| `chat`       | `input.message` is required and must be 20,000 characters or fewer.            |

When an agent has multiple lanes, omitting `laneId` returns `lane_required`.

## Idempotency

Pass an `idempotencyKey` when an external event might retry.

For the same API key, workspace, agent, and idempotency key, Erstan returns the existing run instead of starting a duplicate.

```json theme={null}
{
  "laneId": "start_ocr_quote_intake",
  "input": {
    "sourceRecordType": "customrecord_email_plugin_message",
    "sourceRecordId": "98342",
    "emailBody": "Please quote 12 of competitor part ABC-100."
  },
  "idempotencyKey": "netsuite-email-plugin-quote-98342"
}
```

## Attachments

Runs can include attachments when the selected lane reports `attachments.supported: true`.

```json theme={null}
{
  "laneId": "start_ocr_quote_intake",
  "input": {
    "sourceRecordType": "customrecord_email_plugin_message",
    "sourceRecordId": "98342",
    "emailBody": "See attached PDF."
  },
  "attachments": [
    {
      "name": "quote-request.pdf",
      "type": "application/pdf",
      "base64": "JVBERi0xLjQK..."
    }
  ]
}
```

The request body limit for public API routes is 25 MB. The lane's `attachments.maxRequestBytes` repeats that limit for clients building dynamic forms.

## Start response

New runs return `202 Accepted`.

```json theme={null}
{
  "runId": "0cfd96a4-3e9b-40f2-9a62-8b5ec8ab19f8",
  "agentId": "clx_agent_123",
  "laneId": "start_ocr_quote_intake",
  "status": "running"
}
```

If the request has an `idempotencyKey` that already started a run for the same API key, workspace, and agent, Erstan returns `200 OK` with the existing `runId`.

## Poll response

```http theme={null}
GET /v1/public/runs/{runId}
Authorization: Bearer ers_live_...
```

```json theme={null}
{
  "runId": "0cfd96a4-3e9b-40f2-9a62-8b5ec8ab19f8",
  "agentId": "clx_agent_123",
  "laneId": "start_ocr_quote_intake",
  "status": "completed",
  "finalResponse": {
    "classification": {
      "is_quote_request": true,
      "confidence": "medium",
      "reason": "Email body and attached PDF include customer order lines."
    },
    "decision": {
      "readyToCreatePendingQuote": false,
      "blockingIssues": [
        "Customer could not be matched",
        "Line 2 item is ambiguous"
      ]
    }
  },
  "error": null,
  "startedAt": "2026-05-26T00:00:00.000Z",
  "completedAt": "2026-05-26T00:00:18.000Z"
}
```

`finalResponse` is the agent's final output. Agents intended for automation should be configured to return JSON so NetSuite or another caller can parse the result deterministically.

## Statuses

| Status      | Meaning                                                      |
| ----------- | ------------------------------------------------------------ |
| `running`   | The run is active.                                           |
| `waiting`   | The run is waiting on auth, approval, a tool, or user input. |
| `completed` | The run finished and `finalResponse` is available.           |
| `failed`    | The run failed; read `error`.                                |
| `cancelled` | The run was cancelled.                                       |

Treat `completed`, `failed`, and `cancelled` as terminal.
