> For the complete documentation index, see [llms.txt](https://docs.selcier.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.selcier.xyz/build-on-selcier/typescript-client.md).

# TypeScript client

Selcier's official TypeScript package, with typed resources, automatic paging, error classes and on-chain helpers built for viem.

The official Selcier API client is `@selcier/sdk`. Each endpoint on the [REST API](/build-on-selcier/rest-api.md) page is available as a typed method. The package also takes care of retries and pagination, verifies webhook signatures, and includes the ABIs and helpers for reading Selcier contracts straight from Robinhood Chain.

## Getting the package

```bash
npm install @selcier/sdk
```

Supported runtimes are Node.js 18 and later, Bun and Deno. Because the API client relies on secret keys, it runs on the server side only. `@selcier/sdk/onchain` is different: its helpers hold no keys, so browser use is safe.

## Creating a client

```ts
import { Selcier } from "@selcier/sdk";

const selcier = new Selcier(process.env.SELCIER_SECRET_KEY!);
```

Your key prefix picks the environment: `https://api.selcier.xyz/v1` for `sk_live_` keys, and the sandbox for `sk_test_` keys. Options go in an optional second argument:

```ts
const selcier = new Selcier(process.env.SELCIER_SECRET_KEY!, {
  apiVersion: "2026-10-01", // sent as Selcier-Version
  maxRetries: 3,            // network errors, 429 and 5xx
  timeoutMs: 30_000,
});
```

In production, pin `apiVersion`. That way a change to your account-level default cannot alter the response shapes your code sees. [Keys and environments](/build-on-selcier/keys-and-environments.md) covers versioning in full.

## Available resources

The methods follow the REST resources one for one. Parameters keep the `snake_case` names the API uses, and what comes back is a typed object.

| Methods                                  | SDK properties                                                                          |
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
| `list`, `create`, `retrieve`             | `selcier.companies`, `selcier.shareClasses`                                             |
| `list`, `create`, `retrieve`, `listLots` | `selcier.holders`                                                                       |
| `list`, `create`, `close`                | `selcier.offerings`                                                                     |
| `list`, `create`, `cancel`, `listOrders` | `selcier.liquidityWindows`                                                              |
| `list`, `create`, `del`                  | `selcier.webhookEndpoints`                                                              |
| `list`, `create`                         | `selcier.distributions`, `selcier.proposals`, `selcier.documents`, `selcier.valuations` |
| `list`                                   | `selcier.events`                                                                        |
| `retrieve`                               | `selcier.contracts`                                                                     |
| `check`                                  | `selcier.transfers`                                                                     |
| `constructEvent`                         | `selcier.webhooks`                                                                      |

Here is a representative server-side call:

```ts
const offering = await selcier.offerings.create(
  {
    share_class: "cls_3VbT9pLm",
    price_per_share: "18.40",
    min_raise: "2000000",
    max_raise: "5000000",
    min_ticket: "25000",
    opens_at: "2026-11-10T14:00:00Z",
    closes_at: "2026-11-24T22:00:00Z",
    eligible_claims: ["IDENTITY_VERIFIED", "INVESTOR_ELIGIBLE"],
    documents: ["doc_Lk2m9QpZ"],
  },
  { idempotencyKey: "series-b-extension-2026-11" },
);
```

Options on any write method can include `{ idempotencyKey }`. Without one, the SDK makes a key for that call and sends the same key on each of its own retries, so automatic retrying cannot perform an action twice.

## Walking through pages

By default, a list method gives you one page. Use `for await` on the result to reach every object, and further pages load as the loop advances.

```ts
for await (const holder of selcier.holders.list({ share_class: "cls_3VbT9pLm", limit: 100 })) {
  console.log(holder.id, holder.status);
}
```

If you would rather have an array, with an upper bound:

```ts
const holders = await selcier.holders
  .list({ share_class: "cls_3VbT9pLm" })
  .autoPagingToArray({ limit: 5000 });
```

## Handling failures

Failures surface as exceptions derived from `SelcierError`. On it you will find `type`, `code`, `message`, `statusCode` and `requestId`, which correspond to the [API's error object](/build-on-selcier/rest-api.md#error-responses).

| Class                        | Raised for                  |
| ---------------------------- | --------------------------- |
| `SelcierInvalidRequestError` | `invalid_request`           |
| `SelcierAuthenticationError` | `authentication`            |
| `SelcierRuleViolationError`  | `rule_violation`            |
| `SelcierNotFoundError`       | `not_found`                 |
| `SelcierRateLimitError`      | `rate_limited`              |
| `SelcierChainError`          | `chain_error`               |
| `SelcierConnectionError`     | The absence of any response |

```ts
import { SelcierRuleViolationError } from "@selcier/sdk";

try {
  await selcier.offerings.close("off_5kLp2Wq");
} catch (err) {
  if (err instanceof SelcierRuleViolationError) {
    console.warn(`Blocked by ${err.code}`, err.requestId);
  } else {
    throw err;
  }
}
```

If a transaction went out and then reverted, `SelcierChainError` also provides `txHash`.

## Verifying deliveries

`selcier.webhooks.constructEvent(rawBody, signatureHeader, secret)` handles three jobs in one call:

1. Validating the `Selcier-Signature` header.
2. Enforcing a timestamp tolerance, five minutes by default.
3. Producing a typed event.

Any verification failure raises `SelcierSignatureVerificationError`. JSON that has already been parsed will not work, so hand it the unparsed body as a `Buffer` or string. A complete handler is shown in [Event delivery](/build-on-selcier/event-delivery.md).

## Reading the chain directly

For use with [viem](https://viem.sh), `@selcier/sdk/onchain` provides ABIs, reason code constants and a few small helpers. They suit cases like a wallet or venue front end, where you would rather read state from Robinhood Chain itself than go through the API.

| Export                             | What it gives you                                                                                                          |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `shareTokenAbi`                    | The ABI of `SelcierShareToken`. ERC-20, ERC-3643 and ERC-8056 functions are part of it                                     |
| `ruleEngineAbi`                    | Core contract ABI: `RuleEngine`                                                                                            |
| `identityRegistryAbi`              | Core contract ABI: `IdentityRegistry`                                                                                      |
| `valuationOracleAbi`               | Core contract ABI: `ValuationOracle`                                                                                       |
| `factoryAbi`                       | Core contract ABI: `SelcierFactory`                                                                                        |
| `reasonCodes`                      | All [outcome codes](/build-on-selcier/transfer-outcomes.md) as `bytes32` constants, as well as `decodeReasonCode(bytes32)` |
| `robinhoodChain`                   | Definition viem can use for chain ID `4663`                                                                                |
| `robinhoodChainTestnet`            | Definition viem can use for chain ID `46630`                                                                               |
| `toDisplayAmount(raw, multiplier)` | Applies the current multiplier to a raw balance, producing shares                                                          |

The following example reads `canTransfer` and `uiMultiplier` ahead of building a transfer:

{% code title="check-transfer.ts" %}

```ts
import { createPublicClient, http, parseUnits } from "viem";
import {
  shareTokenAbi,
  decodeReasonCode,
  robinhoodChain,
  toDisplayAmount,
} from "@selcier/sdk/onchain";

const client = createPublicClient({
  chain: robinhoodChain,
  transport: http(process.env.ROBINHOOD_RPC_URL),
});

const token = "0x..." as const; // from GET /v1/contracts or the Explorer
const from = "0x1111111111111111111111111111111111111111" as const;
const to = "0x2222222222222222222222222222222222222222" as const;

const [[ok, reason], multiplier, raw] = await Promise.all([
  client.readContract({
    address: token,
    abi: shareTokenAbi,
    functionName: "canTransfer",
    args: [from, to, parseUnits("100", 18)],
  }),
  client.readContract({ address: token, abi: shareTokenAbi, functionName: "uiMultiplier" }),
  client.readContract({ address: token, abi: shareTokenAbi, functionName: "balanceOf", args: [from] }),
]);

console.log("Shares held:", toDisplayAmount(raw, multiplier));

if (!ok) {
  console.log("Transfer would fail:", decodeReasonCode(reason)); // e.g. "HOLDING_PERIOD_ACTIVE"
}
```

{% endcode %}

Production traffic calls for a dedicated RPC provider, since the public Robinhood Chain endpoint is rate-limited. See [The network underneath](/under-the-hood/network-foundation.md).

## Releases and help

Releases use semantic versioning. Every major version targets a particular API version date, and the changelog on [GitHub](https://github.com/selcier) lists which one. If something looks broken, file a GitHub issue. You can also email <support@selcier.xyz>.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.selcier.xyz/build-on-selcier/typescript-client.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
