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

# JavaScript SDK reference

> Client configuration, runtime and authoring methods, errors, retries, and advanced MCP access.

This reference describes `@erstan/sdk@0.1.0-beta.1`, a JavaScript ESM package with
TypeScript declarations for Node.js 22+. See the [quickstart](/developers/sdk-quickstart)
for current private-beta availability and installation.

## Client configuration

```js theme={null}
import { ErstanClient } from '@erstan/sdk';

const client = new ErstanClient({
  apiKey: process.env.ERSTAN_API_KEY,
  apiBase: 'https://api.erstan.com',
  timeoutMs: 60_000,
  maxRetries: 2,
  retryDelayMs: 500,
  maxRetryDelayMs: 30_000,
});
```

| Option            | Behavior                                                                                                                                      |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`          | Key string or synchronous/async credential provider. Defaults to `ERSTAN_API_KEY`. Keep it in trusted local/server code.                      |
| `apiBase`         | API origin only. Defaults to `ERSTAN_API_BASE`, then production. HTTPS is required except for loopback development. Set it explicitly for QA. |
| `timeoutMs`       | Total deadline per API operation, including credentials, response body, and retries; default 60,000 ms.                                       |
| `maxRetries`      | Retries for eligible operations, not all writes; default 2.                                                                                   |
| `retryDelayMs`    | Initial retry delay; default 500 ms.                                                                                                          |
| `maxRetryDelayMs` | Maximum retry delay; default 30,000 ms. A longer `Retry-After` returns the error instead of retrying too early.                               |
| `fetch`           | Optional Fetch-compatible implementation, useful for application adapters and offline tests.                                                  |

Every method accepts request options as its last argument: `signal`, `timeoutMs`,
and `maxRetries`. Waiting methods also accept `waitTimeoutMs` (default 300,000 ms)
and `pollIntervalMs` (default 1,000 ms). The wait deadline starts after launch or
continuation acceptance, or on entry to `wait`; it does not include submission.
`traceEvents` additionally accepts `maxPages` (default 1,000).

Convenience run/wait operations pin one credential for the sequence. Separate
calls use the provider's current key. Erstan restricts run access to the exact
key that created the run, so replacing a key is not a recovery path for old runs.

## Published Agents and runs

All methods below are on `client`. Optional request options are omitted from
the table unless needed to distinguish a signature.

| Method                                        | Returns / purpose                                                                         |
| --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `agents.listPublished(options?)`              | `{ agents }`, the currently runnable published inventory; not paginated.                  |
| `agents.getPublished(agentId, options?)`      | Published Agent details, lanes, input schemas, and attachment policy.                     |
| `agents.run(agentId, body, options?)`         | Run handle with `runId`, `agentId`, `laneId`, and `status`.                               |
| `agents.runAndWait(agentId, body, options?)`  | Starts a run, then returns a `RunWaitResult`.                                             |
| `runs.get(runId, options?)`                   | Current run status, output, and any pending interaction.                                  |
| `runs.wait(runId, options?)`                  | Observes an existing run until a terminal result, actionable interaction, or other pause. |
| `runs.continue(runId, body, options?)`        | Sends a follow-up to a terminal run; returns the continuation's run handle.               |
| `runs.continueAndWait(runId, body, options?)` | Continues and follows the returned run ID, including forks.                               |
| `runs.reply(runId, body, options?)`           | Answers the exact current user-input interaction.                                         |
| `runs.decideApproval(runId, body, options?)`  | Submits an explicit approve/reject decision for the current approval interaction.         |
| `runs.cancel(runId, options?)`                | Requests remote cancellation, which may initially report `cancelling`.                    |
| `runs.trace(runId, query?, options?)`         | One bounded page of persisted trace events.                                               |
| `runs.traceEvents(runId, query?, options?)`   | Async iterator over persisted trace events with bounded paging.                           |

Run bodies contain `input`, optional `laneId`, `attachments`, and
`idempotencyKey`. Select the Agent and lane intentionally; do not pick the first
available Agent automatically. Attachments use `{ name, type, base64 }` or
`{ name, type, storageKey }`, subject to the lane policy and 25 MB request limit.
The SDK does not read local file paths or upload files implicitly. Runtime
control fields such as Skill selections do not belong in business `input`.

Continuation bodies include `message` and may include an `idempotencyKey`.
Always follow the **returned** run ID. See [Run Agents](/developers/run-agents)
for continuation and lane constraints.

### Waiting and human interactions

`RunWaitResult.reason` is `completed`, `failed`, `cancelled`, `interaction`,
`waiting`, or `unknown_status`. The result includes `run`; `finalResponse` is
typed as `unknown`. Failed Agent execution is a result, not an HTTP exception.

Only an `actionable` pending interaction can be answered. `reply` uses the
current `interactionId` plus `message` or structured `answers`; `decideApproval`
uses `{ interactionId, action: 'approve' | 'reject' }`. Retained/resuming
interrupts and asynchronous batches keep polling. Other pauses and unknown
future statuses return control. Nothing auto-approves or answers for the user.

Aborting a request or timing out observation does **not** cancel remote work.
Persist accepted run IDs and resume with `runs.wait`. Cancellation is a separate
intentional call and cannot undo external effects that already occurred.

### Durable trace paging

Trace queries accept `cursor`, `limit`, `eventTypes`, `includePayloads`, and
`payloadMaxChars`. Payloads are opt-in and remain potentially sensitive even
after server redaction. Sequence values stay strings.

```js theme={null}
// client is an ErstanClient; runId identifies a run started with the same key.
for await (const event of client.runs.traceEvents(runId, {
  eventTypes: ['node_completed'],
  includePayloads: false,
}, { maxPages: 100 })) {
  console.log(event.sequence, event.eventType);
}
```

This is durable event retrieval, not a live token stream or an indefinite tail.
The iterator stops at the last available page, preserves filters, and rejects
repeated cursors. Reaching its page limit raises a protocol error; use `trace`
with an explicit cursor for manual paging. Breaking iteration does not cancel
the run.

## Agent and Skill authoring

`context.get()` returns `workspaceId`, actor, scopes, available teams, and
capabilities. Check the expected workspace before making changes. The beta SDK
requires `stagedAuthoring: true` and `authoringContract: '2026-09-09.1'` for
authoring. Preview additionally requires `guardedPreviews`; explicit Skill
selections also require `draftSkillOverrides`. An older or disabled server
raises `ErstanCapabilityError` before the authoring call. Published runtime
methods do not depend on staged-authoring support.

| Method                                                                                    | Returns / purpose                                                                                            |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `context.get(options?)`                                                                   | Workspace identity, access context, and capabilities.                                                        |
| `agents.list(query?, options?)` / `skills.list(query?, options?)`                         | Readable authoring inventory, including drafts, with `agents` or `skills`, `limit`, and opaque `nextCursor`. |
| `agents.get(id, options?)` / `skills.get(id, options?)`                                   | Current authoring response with `agent` or `skill`, revision, current/published versions, and permissions.   |
| `agents.create(body, writeOptions)` / `skills.create(body, writeOptions)`                 | Saves a new draft.                                                                                           |
| `agents.update(id, body, guardedOptions)` / `skills.update(id, body, guardedOptions)`     | Saves changes as a draft without promoting published content.                                                |
| `agents.validate(body, options?)` / `skills.validate(body, options?)`                     | Validation result with `valid` and diagnostics; no save, execution, or publication.                          |
| `agents.publish(id, guardedOptions)` / `skills.publish(id, guardedOptions)`               | Validates and publishes the exact approved current content.                                                  |
| `agents.listVersions(id, query?, options?)` / `skills.listVersions(id, query?, options?)` | Paginated version summaries.                                                                                 |
| `agents.getVersion(id, version, options?)`                                                | Historical Agent authoring response; its history revision cannot authorize current writes.                   |
| `skills.getVersion(id, version, options?)`                                                | Historical Skill version object and complete package, not a current write guard.                             |
| `agents.preview(id, body, guardedOptions)`                                                | Starts a real hosted preview of the exact guarded remote draft; returns a run handle.                        |
| `catalog.nodes(query?, options?)`                                                         | Live node definitions and field detail.                                                                      |
| `catalog.tools(query?, options?)` / `catalog.skills(query?, options?)`                    | Live pinnable tool/Skill catalogs, not the complete draft authoring inventory.                               |
| `catalog.guide(options?)`                                                                 | Current authoring rules.                                                                                     |

`agents.list/get` are authoring reads, not aliases for `listPublished/getPublished`.
Authoring list queries accept `cursor`, `limit`, and `query`. Pass `nextCursor`
unchanged for the next page; do not invent offset pagination. Catalog queries
and node fields follow the included TypeScript declarations and live guide.
Catalog discovery is currently unavailable to team-restricted keys; do not
broaden a key automatically to work around that restriction.

Agent creation requires a target `teamId`, `name`, and complete `nodes` and
`edges`. Updates replace supplied node/edge arrays in full; preserve unrelated
content and unknown graph fields. Agent validation accepts proposed content or
an `agentId`. Skill creation and updates use a complete `packageJson`; create
also requires `name`. Skill validation accepts `{ packageJson, ... }`.
Preserve every related file, action declaration, and extension field. Full
offline JSON/ZIP codecs are in the separate `@erstan/skill-package` package.

### Permissions and lifecycle

| SDK operation                              | Required API-key scopes                       |
| ------------------------------------------ | --------------------------------------------- |
| Authoring reads/history/catalogs           | `agents:read`                                 |
| Authoring inventory lists                  | `agents:read` + `agents:list`                 |
| Creation, updates, validation, publication | `agents:read` + `agents:write`                |
| Draft preview                              | `agents:read` + `agents:write` + `agents:run` |
| Observe preview runs and traces            | `runs:read`, using the creating key           |

These are additive to current workspace/team permissions and server
capabilities. A key that can validate through REST also has authoring write
authority; it is not a validation-only credential for untrusted PR code.

Saving stages Skill content while the previous published package remains live.
Agent name, description, category, tags, difficulty, and estimated time stage
with the graph and promote together; behavior type stays immutable after first
publication. Validation, preview, and publication are independent operations.
Publishing does not require a successful test receipt or a particular workflow.

Publish required Skills explicitly before the Agent. Agent publication never
publishes dependencies for you. Publishing a shared Skill affects future
consumers, while snapshot-backed admitted runs and their continuations retain
their selected packages. Legacy runs without snapshot evidence cannot be
assumed to have that guarantee. Multiple resource writes are not an atomic
deployment.

### Guards, idempotency, and preview

`writeOptions` requires `{ idempotencyKey }`. `guardedOptions` requires both
`{ revision, idempotencyKey }`; the SDK sends the revision as `If-Match`.
Keep content and its returned revision together. Historical version reads are
read-only. After a 412 conflict, re-read and reconcile rather than replacing
the guard on stale local edits.

```js theme={null}
// Use the baseline paired with the content you reviewed; this saves a draft only.
const { agent } = await client.agents.get(agentId);
const saved = await client.agents.update(agentId, {
  description: 'Revised description for review.',
}, { revision: agent.revision, idempotencyKey: updateKey });
// Persist saved.agent content and saved.agent.revision together for the next step.
```

Preview bodies contain `input`, optional `laneId`, and optional top-level
`skillVersions: [{ skillId, version }]`. Each selected Skill requires edit
permission and must fit the Agent's existing Skill policy. Unselected Skills
and child Agents retain published content. Preview never runs unsaved local
files, auto-publishes dependencies, or bypasses approvals. It seals the selected
graph; later edits or publication can fork and advance the version. Preview
can incur costs and external effects, so invoke it only deliberately.

For identical authoring intent, same-key response recovery is available for
24 hours; retained tombstones prevent duplicate dispatch afterward. This is
at-most-once dispatch, not multi-resource atomicity. A lost response,
`authoring_outcome_unknown`, or expired receipt requires reconciliation before
any new mutation. Never blindly generate a fresh key to repeat an uncertain
write, or reuse a key with changed content or guards.

## Errors and retry behavior

`ErstanError` is the base error. `ErstanAPIError` exposes `status`, `code`,
`details`, `requestId`, `eventId`, and `retryAfterMs` when available. Subclasses
distinguish authentication, permission, not-found, validation, conflict,
capability, and rate-limit errors. Configuration, connection, timeout, abort,
and protocol errors also have distinct classes. `ErstanWaitTimeoutError`
includes `runId` and the last observed run when available; other observation
errors may also carry them. See [API errors](/developers/errors) for server codes.

Safe reads retry transient network errors and HTTP 408/429/500/502/503/504.
Run starts and continuations retry only with a non-empty body `idempotencyKey`.
Cancellation is idempotent. Replies and approval decisions never retry
automatically; inspect the current interaction after uncertainty. Authoring
mutations use their explicit keys and guarded receipt contract. Authorization,
validation, and conflict errors are not resolved by automatic retry.

## Advanced MCP access

```js theme={null}
import { ErstanMcpClient } from '@erstan/sdk/mcp';

const mcp = new ErstanMcpClient({ apiBase: 'https://api.erstan.com' });
const { tools } = await mcp.listTools();
const result = await mcp.callTool('get_agent_builder_guide');
if (result.isError) {
  throw new Error('Inspect the MCP tool error before continuing.');
}
```

`listTools(query?, options?)`, `callTool(name, args?, options?)`, and
`request(method, params?, options?)` target Erstan's stateless JSON-response
`/v1/mcp` endpoint using the same API-key configuration. This is not a generic
MCP transport or OAuth client: SSE, sessions, and notifications are unsupported.
Tool calls are not automatically retried, and `isError` remains part of the
result. JSON-RPC failures raise `ErstanMcpError` from the MCP subpath.

Use the live tool schema: MCP Skill updates/publication use `expectedVersion`,
not REST revision headers, and MCP Skill mutations have no idempotency argument.
REST/SDK history and cancellation methods do not imply equivalent MCP tools.
For host-managed OAuth, use the [plugin](/developers/connect-coding-agent).
