> For the complete documentation index, see [llms.txt](https://stair-ai.gitbook.io/stair-ai-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://stair-ai.gitbook.io/stair-ai-docs/sdk/api-reference.md).

# API reference

The canonical surface of `@stairai/ledger-sdk` v1. Symbols not listed here are internal and may change without notice. The TypeScript signatures below are illustrative; the Python SDK (also v1) and the Go SDK (v2) expose the same identifiers and shapes in their host language idioms.

## Surface map

| Group          | Symbols                                                                                                                                                                                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Static factory | `LedgerClient.registerAgent`, `LedgerClient.resolveAgentId`                                                                                                                                                        |
| Client         | `LedgerClient` constructor; `submit`, `submitBatch`, `getRecord`, `getSession`, `getTrace`, `newSession`                                                                                                           |
| Session        | `Session.id`, `Session.submit`, `Session.submitBatch`                                                                                                                                                              |
| Helpers        | `newRecordId`, `nowEpochMs`, `isValidRecordId`                                                                                                                                                                     |
| Config types   | `LedgerClientConfig`, `RetryConfig`, `WalletConfig`, `AgentWalletInput`, `ModelInvocation`, `AgentMetadata`                                                                                                        |
| Response types | `RecordAck`, `BatchAck`, `RecordError`, `AgentRegistration`                                                                                                                                                        |
| Errors         | `LedgerError` (base), `ValidationError`, `AuthError`, `RateLimitError`, `NetworkError`, `ServerError`, `IdempotencyConflictError`, `NotFoundError`                                                                 |
| Record types   | `ObservingRecord`, `PlanningRecord`, `ThinkingRecord`, `ActingRecord`, `ReflectingRecord`, `ToolCallingRecord`, `OtherRecord` (re-exported; see [behavior taxonomy](/stair-ai-docs/concepts/behavior-taxonomy.md)) |

## Static factory

### `LedgerClient.registerAgent(opts) → AgentRegistration`

Register a new agent under an existing owner. Provisions a per-agent anchor wallet in the owner's wallet mode.

| Parameter  | Type                | Notes                                                                                                                        |
| ---------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`   | string              | Owner-level api\_key (issued out-of-band)                                                                                    |
| `name`     | string              | Partner-chosen; unique within owner                                                                                          |
| `wallet`   | `AgentWalletInput?` | BYOW only; optional. In BYOW with no `wallet`, the agent inherits the owner default. In custodial mode the field is ignored. |
| `metadata` | `AgentMetadata?`    | Display fields (description, website, tags)                                                                                  |

Idempotent on `(owner, name)`. Repeat returns the existing agent. A `wallet` argument in a repeat is ignored.

Errors: `AuthError`, `ValidationError`, `NetworkError`, `ServerError`.

### `LedgerClient.resolveAgentId(opts) → string`

Look up an existing `agent_id` by name. Best practice: call once at startup, cache the result.

| Parameter | Type   |
| --------- | ------ |
| `apiKey`  | string |
| `name`    | string |

Errors: `AuthError`, `NotFoundError`, `NetworkError`, `ServerError`.

## Client

### `new LedgerClient(config: LedgerClientConfig)`

Constructs a client bound to a single agent. No network call; configuration is validated lazily on first request.

### `client.submit(record) → RecordAck`

Submit one record. SDK fills `record_id`, `schema_version`, and `client_ts_utc` if omitted. `agent_id` is injected from the client and ignored if supplied on the record.

Validation runs locally; on failure raises `ValidationError` synchronously without contacting the server. Retries reuse the same `record_id`.

Errors: `ValidationError`, `AuthError`, `RateLimitError`, `NetworkError`, `ServerError`, `IdempotencyConflictError`.

### `client.submitBatch(records) → BatchAck`

Submit up to 50 records in one request. Per-record validation runs locally; only locally-valid records are sent. Partial server-side failure does **not** raise — inspect `BatchAck.results` for per-record errors. Batch-level failures (auth, oversized batch, transport) raise as for `submit`.

### `client.getRecord(record_id) → Record`

Fetch a single stored record by `record_id`.

Errors: `AuthError`, `NotFoundError`, `NetworkError`, `ServerError`.

### `client.getSession(session_id) → SessionFetch`

Fetch every record under `(agent_id, session_id)`, ordered by `server_ts_utc` ascending.

```
SessionFetch:
  session_id: string
  records:    Record[]
```

### `client.getTrace(opts?) → TracePage`

Paginated read of the calling agent's full trace.

| Option   | Type   | Default | Notes                                             |
| -------- | ------ | ------- | ------------------------------------------------- |
| `before` | string | —       | Record ID cursor; returns records older than this |
| `limit`  | number | 100     | Max 500                                           |

```
TracePage:
  records:     Record[]
  next_cursor: string | null
```

### `client.newSession(session_id?) → Session`

Local-only convenience. Returns a `Session` bound to a session\_id (caller-supplied or SDK-generated). No network call.

## Session

A `Session` is local SDK sugar that pins a `session_id` so callers don't have to pass it on every record. There is no server-side session lifecycle.

| Symbol                                    | Behavior                                                                    |
| ----------------------------------------- | --------------------------------------------------------------------------- |
| `session.id: string`                      | Read-only; the bound session\_id                                            |
| `session.submit(record) → RecordAck`      | Same as `client.submit`, with session\_id auto-injected                     |
| `session.submitBatch(records) → BatchAck` | Same as `client.submitBatch`, with session\_id auto-injected on each record |

## Helpers

| Function                 | Returns        | Use for                                                                                                                    |
| ------------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `newRecordId()`          | UUID v4 string | Construct dependency edges where a child needs to reference an as-yet-unsubmitted record                                   |
| `nowEpochMs()`           | integer        | Stamp client timestamps consistent with SDK internals                                                                      |
| `isValidRecordId(value)` | boolean        | Validate UUIDs read from external systems before passing to `upstream_record_id`, `parent_record_id`, or `input_record_id` |

## Configuration types

```
LedgerClientConfig:
  apiKey:                  string
  agentId:                 string
  environment?:            "production" | "staging" | "development"  (default "production")
  endpoint?:               string  (override Trace Service base URL)
  defaultModelInvocation?: ModelInvocation
  retry?:                  RetryConfig
  httpTransport?:          HttpTransport  (test/instrumentation hook)

RetryConfig:
  attempts:    number          (default 3)
  backoffMs:   number[]        (default [500, 1000, 2000])

WalletConfig (tagged union — used by owner-onboarding tooling, not LedgerClientConfig):
  CustodialWalletConfig:  { mode: "custodial" }
  ByowWalletConfig:       { mode: "byow", address: string,
                            signer?: (txBytes: bytes) => bytes }

AgentWalletInput (passed to LedgerClient.registerAgent in BYOW mode):
  address: string                              (partner-owned address recorded as the agent's anchor author)
  signer?: (txBytes: bytes) => bytes           (optional partner signer for v1 anchoring)

ModelInvocation:
  provider:       string
  model_name:     string
  model_version?: string
  tokens_in?:     number
  tokens_out?:    number
  cost_usd?:      number
  temperature?:   number
  finish_reason?: string

AgentMetadata:
  description?: string
  website?:     string
  tags?:        string[]
```

`WalletConfig` is **not** a field on `LedgerClientConfig` — wallet mode is established at owner registration and is not reconfigured at client construction.

## Response types

```
RecordAck:
  record_id:      string   (echoes the UUID the SDK submitted)
  session_id:     string
  server_ts_utc:  number   (epoch ms; authoritative timestamp)
  is_duplicate:   boolean  (true if (agent_id, record_id) was already on file)

BatchAck:
  batch_id:  string
  results:   (RecordAck | RecordError)[]   (same order as submitted batch)

RecordError:
  record_id: string
  code:      string   (stable, machine-readable)
  message:   string

AgentRegistration:
  agent_id:               string
  name:                   string
  agent_wallet_address:   string
  created_at:             number
```

## Errors

All errors carry a stable `code` and an optional `details` map.

| Class                      | `code`                          | When raised                                                                                                            |
| -------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `ValidationError`          | `validation_failed`             | Local schema check failed; the record never reached the network. `details.field` and `details.reason` carry specifics. |
| `AuthError`                | `auth_invalid` / `auth_expired` | API key rejected.                                                                                                      |
| `RateLimitError`           | `rate_limited`                  | `details.retry_after_ms` carries suggested wait.                                                                       |
| `NetworkError`             | `network_failed`                | Request never reached the server after exhausting retries.                                                             |
| `ServerError`              | `server_5xx`                    | `details.status` carries HTTP status.                                                                                  |
| `IdempotencyConflictError` | `record_id_conflict`            | Same `record_id` previously submitted with a different body.                                                           |
| `NotFoundError`            | `not_found`                     | Lookup target does not exist or is not visible to the calling owner.                                                   |

Partners may branch on either the class hierarchy or on `code`. New subclasses may be added in minor versions; forward-compatible code should fall through to `LedgerError`.

## Validation

### Client-side (hard errors, raised before network call)

* Valid `behavior` value
* Non-empty `schema_version`, `session_id`
* `record_id` is a valid UUID v4
* `client_ts_utc` is a positive integer (epoch ms)
* `Thinking.prompt` and `Thinking.output_payload` non-empty
* `Acting.execution_id` present when `target_system` is public-chain and `execution_status === "confirmed"`
* All size limits respected (see below)

### Server-side

* API key matches `agent_id` owner
* `schema_version` is supported
* `(agent_id, record_id)` unique (duplicate → original ack with `is_duplicate: true`)
* ≤1 `Acting` per session
* `parent_record_id` and every `upstream_record_id` resolve under the same `agent_id`
* Batch ≤50; size limits re-checked

### Size limits (v0.1; will tighten in v1 based on telemetry)

| Scope                                              | Cap                   |
| -------------------------------------------------- | --------------------- |
| Per-record total (JSON-encoded)                    | 64 KB                 |
| Per-batch total (JSON-encoded)                     | 1 MB                  |
| `notes`                                            | 2 KB                  |
| `tags`                                             | 32 entries × 64 chars |
| `tool_meta`, `input_payload`, `parameters`, `data` | 16 KB                 |
| `output_payload` (ToolCalling, Thinking)           | 32 KB                 |
| `prompt` (Thinking)                                | 16 KB                 |
| `trigger_payload_summary` (Observing)              | 4 KB                  |

Larger payloads will be supported in v1 via out-of-band content addressing (the record carries a content hash + retrieval URI). v0.1 partners must summarize in-line.

## HTTP API (for non-SDK consumers)

The SDK is the supported integration; the underlying HTTP API is documented for reference.

| Endpoint                            | Purpose                                     |
| ----------------------------------- | ------------------------------------------- |
| `POST /v1/agents`                   | Register an agent (auth: `api_key`)         |
| `GET /v1/agents?name=...`           | Resolve agent\_id by name                   |
| `GET /v1/agents/:agent_id`          | Fetch agent metadata                        |
| `PATCH /v1/agents/:agent_id`        | Update agent metadata                       |
| `POST /v1/records`                  | Submit one record                           |
| `POST /v1/records:batch`            | Submit ≤50 records; partial failure allowed |
| `GET /v1/records/:id`               | Fetch a record                              |
| `GET /v1/sessions/:id?agent_id=...` | Fetch all records in a session              |
| `GET /v1/traces/:agent_id`          | Paginated trace                             |

Owner endpoints (`POST /v1/owners`, `PATCH /v1/owners/me`, `POST /v1/owners/me/rotate-key`) are website/admin only and not exposed via SDK.

## Schema source of truth

Records are authored once in **JSON Schema 2020-12** at `schemas/records.schema.json`. TypeScript, Python, and Go bindings are generated from that file and checked in alongside it. Adding a new behavior or field is a JSON Schema edit followed by regeneration — never a hand-edit on a language binding.

The SDK ships the canonical schema bundled, so partners can re-validate independently of the network.

## Versioning

The SDK stamps `schema_version` on every record from a bundled constant (`"1.0"` in v0.1). The server accepts supported past versions during migration windows, so partners are not forced into breaking flag days.
