> ## 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.

# Quickstart

> Create an API key, run an agent lane, and read the result.

This guide assumes you already have a published agent in an Erstan workspace.

## 1. Create an API key

In Erstan, open **Settings -> API Keys -> Create Key**.

Select these scopes:

```text theme={null}
agents:list
agents:run
runs:read
```

Agent access follows your current workspace and team permissions. Copy the `ers_live_...` secret when it is shown.

## 2. Set environment variables

```bash theme={null}
export ERSTAN_API_BASE="https://api.erstan.com"
export ERSTAN_API_KEY="ers_live_..."
```

## 3. List available agents

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/agents" \
  -H "Authorization: Bearer $ERSTAN_API_KEY"
```

Response:

```json theme={null}
{
  "agents": [
    {
      "id": "clx_agent_123",
      "name": "OCR Quote Intake",
      "description": "Extract quote data from emails and attachments.",
      "behaviorType": "pipeline",
      "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",
      "updatedAt": "2026-05-26T00:00:00.000Z"
    }
  ]
}
```

If `defaultLaneId` is present, the agent has one public lane and you can omit `laneId` when starting a run.

## 4. Inspect an agent

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/agents/clx_agent_123" \
  -H "Authorization: Bearer $ERSTAN_API_KEY"
```

Use this endpoint when an agent has multiple lanes. The lane `id` returned here is the same lane ID used in the Erstan frontend.

## 5. Start a run

For a single-lane agent that accepts fields:

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/agents/clx_agent_123/runs" \
  -X POST \
  -H "Authorization: Bearer $ERSTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "sourceRecordType": "customrecord_email_plugin_message",
      "sourceRecordId": "98342",
      "emailBody": "Please quote 12 of competitor part ABC-100."
    },
    "idempotencyKey": "netsuite-email-plugin-quote-98342"
  }'
```

For a multi-lane agent, pass the selected `laneId`:

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/agents/clx_agent_123/runs" \
  -X POST \
  -H "Authorization: Bearer $ERSTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "laneId": "start_quote_chat",
    "input": {
      "message": "Summarize the overdue invoices for this customer."
    },
    "idempotencyKey": "customer-123-2026-05-26"
  }'
```

Response:

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

## 6. Poll for results

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/runs/0cfd96a4-3e9b-40f2-9a62-8b5ec8ab19f8" \
  -H "Authorization: Bearer $ERSTAN_API_KEY"
```

Runs execute durably in the background and may continue for hours. Closing the
request, browser, or integration process does not stop the Agent. When the run
completes, `finalResponse` contains the agent's final output.

```json theme={null}
{
  "runId": "0cfd96a4-3e9b-40f2-9a62-8b5ec8ab19f8",
  "agentId": "clx_agent_123",
  "laneId": "start_ocr_quote_intake",
  "status": "completed",
  "finalResponse": {
    "summary": "3 overdue invoices found.",
    "total": 1240.5
  },
  "error": null,
  "startedAt": "2026-05-26T00:00:00.000Z",
  "completedAt": "2026-05-26T00:00:18.000Z",
  "cancelRequestedAt": null,
  "cancelledAt": null
}
```

## 7. Cancel a run

Cancellation is an explicit, idempotent request. A run first reports
`cancelling`; it becomes `cancelled` when the owning worker acknowledges the
signal or its lease expires.

```bash theme={null}
curl "$ERSTAN_API_BASE/v1/public/runs/0cfd96a4-3e9b-40f2-9a62-8b5ec8ab19f8/cancel" \
  -X POST \
  -H "Authorization: Bearer $ERSTAN_API_KEY"
```

Do not treat a disconnected client as cancellation. Cancellation cannot roll
back an external write that completed before the worker received the signal.

## JavaScript example

```js theme={null}
const baseUrl = process.env.ERSTAN_API_BASE || 'https://api.erstan.com';
const apiKey = process.env.ERSTAN_API_KEY;

async function erstan(path, options = {}) {
  const response = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers: {
      authorization: `Bearer ${apiKey}`,
      'content-type': 'application/json',
      ...(options.headers || {})
    }
  });

  const body = await response.json();
  if (!response.ok) {
    throw new Error(body?.error?.message || `Erstan API request failed: ${response.status}`);
  }
  return body;
}

const { agents } = await erstan('/v1/public/agents');
const agent = agents[0];
const lane = agent.lanes.find((candidate) => candidate.mode === 'chat') || agent.lanes[0];

const run = await erstan(`/v1/public/agents/${agent.id}/runs`, {
  method: 'POST',
  body: JSON.stringify({
    laneId: agent.defaultLaneId ? undefined : lane.id,
    input: lane.mode === 'chat'
      ? { message: 'Summarize today\'s AP exceptions.' }
      : {
          sourceRecordType: 'customrecord_email_plugin_message',
          sourceRecordId: '98342',
          emailBody: 'Please quote 12 of competitor part ABC-100.'
        },
    idempotencyKey: `ap-exceptions-${new Date().toISOString().slice(0, 10)}`
  })
});

let result;
do {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  result = await erstan(`/v1/public/runs/${run.runId}`);
} while (['running', 'waiting', 'cancelling'].includes(result.status));

console.log(result.finalResponse);
```
