Skip to main content
@0xinsider/sdk is the TypeScript client for the 0xinsider API. It gives you a typed call for every public operation, an error class per API error, cursor pagination, the resumable SSE stream, and webhook signature checks. Use it when a Node service needs those instead of hand-written fetch calls. The client takes the same Pro key as the CLI and the MCP server. Its source is public at 0xinsider/0xinsider-node, which is also where npm will publish it from.
The first npm release is pending: npm install @0xinsider/sdk answers 404 until it lands. Build it from source for now, or use the Python or Go client, which are published.

Build from source

The client needs Node.js 18 or later, because it uses the built-in fetch, ReadableStream, and node:crypto. It has no runtime dependencies of its own, and it is ESM only.
Then install the build from your project with npm install ../0xinsider-node. Once the package is on npm, npm install @0xinsider/sdk replaces both steps.

What the client handles

Start in the sandbox, without a key

OxinsiderApiClient.sandbox() calls https://0xinsider.com/sandbox/api/v1. Every documented operation answers with example data, nothing is stored, and no credential is needed. Every response carries meta.sandbox: true. Add sandbox_status to a query to get one of the operation’s documented errors, thrown as the class production would throw. A live key is refused in sandbox mode, and a sandbox key from POST /api/v1/agents/register is optional. Streams and file downloads answer 400 there.

Read a trader and search markets

Keep financial values exact

The trader and positions responses keep their display-safe numeric fields for charts and labels, and add exact decimal atoms when the source value is verified. Install a decimal library for arithmetic; do not pass an exact value through Number first.
unit, scale, and basis travel with each atom. The exact block or member can be absent when the source is unavailable; an absent value is not zero.

Typed by the operation

Since 0.3.0 every method knows its own shape, so you do not declare it. trader.data is the route’s Trader, explore.data[0] is an ExploreEntry, explore.facets is typed, and radar.data[0] is a RadarFlag. A batch’s meta is the BatchResponseMeta that carries request_cost and rate_limit. That typing also catches mistakes at compile time. A query key the route does not document is an error: listInsiderRadar({ min_grade: "S" }) fails, because that route takes min_suspicion and severity. A missing path parameter is an error too. The next release adds the canonical spelling of these names: listSuspiciousTrades(), getSuspiciousTrade(), and the types SuspiciousTrade, SuspiciousTradesListParams, SuspiciousTradeFlaggedData and SuspiciousTradeFlaggedEvent. listInsiderRadar(), getInsiderRadarFlag(), InsiderRadarListParams, InsiderRadarFlagRaisedData, InsiderRadarFlagRaisedEvent and RadarFlag all stay as deprecated aliases, so no import breaks. InsiderRadarFlagRaisedEvent keeps its own "insider_radar_flag_raised" discriminant. No method takes a type argument any more. A client.getTrader<MyShape>(...) written against 0.2.x drops the argument. Every method forwards the same transport options after its own parameters: signal, timeoutMs, maxRetries, headers (for an If-None-Match on a conditional read) and, on the keyed writes, idempotencyKey.
call, list, paginate, paginatePages, and collect are typed the same way when you pass the operation id as a literal string. They fall back to a loose form when you pass an id chosen at runtime, or an explicit type argument. The loose form takes plain path, query, and body records, and returns the generic { object, data, meta? } envelope for a caller that asserts the shape itself.

Look up 1 to 25 traders in one request

batchGetTraders() posts { traders, expand } to POST /api/v1/traders/batch. data keeps your request order and returns one row per input, duplicates included. Each row is either status: "ok" with its data or status: "error" with that item’s own error, so one unknown wallet does not fail the request. meta.request_cost counts batch item units, not requests.

Paginate a list

paginate() follows next_cursor and yields each item. paginatePages() yields whole envelopes, for when you need meta or total.
The loop stops when has_more is false. A page that says has_more: true with no next_cursor, or that repeats a cursor already requested, throws PaginationError before it is yielded. Its reason is missing_cursor or repeated_cursor, and the page itself is attached to the error. Three options control the loop. maxPages caps how many pages it fetches and must be a positive integer, and signal cancels it. A progress object records whether the loop stopped at maxPages or at the end of the collection, and which cursor continues from there. A page that still fails after its own retries throws that page’s error. paginationResumePoint() then gives you the cursor to continue from.

Read conditionally

Handle errors

Every response outside 2xx throws a subclass of OxinsiderApiError. The client picks the subclass by the response’s error.reason first, then by its error.code. Every error exposes status, code, reason, retryAt, error, meta, requestId, and the raw body. RateLimitedError, RateLimitUnavailableError, ServerTimeoutError, and PickNotReleasedError add retryAfterSeconds. The client’s own retries run first, so what you catch is the final failure. Read the reason the API sent from err.error.reason, not from err.reason. The reason property is set only on the classes named for a reason in the table above, so a 429 whose body says monthly_quota_exceeded arrives as a RateLimitedError with err.reason still null.

Write with an idempotency key

Eight operations accept an idempotencyKey: createWebhook, updateWebhook, deleteWebhook, rotateWebhookSecret, prepareWebhookSecret, activateWebhookSecret, retireWebhookSecret, and redeliverWebhookDelivery. The key makes the write safe to replay and eligible for retry, and every retry sends the same key and the same bytes. Only createWebhook, updateWebhook, deleteWebhook, and redeliverWebhookDelivery have a convenience method. Reach the four secret-rotation operations through client.call("rotateWebhookSecret", ...) and its siblings. See Webhooks for what staged rotation does. Reuse a key only with the same body. A key on any other operation throws before the request, because the API does not read it there. After a timeout or exhausted retries the outcome is unknown, so send the same key and body once more, or read the resource; a key gives you safe replay, not exactly-once delivery.

Stream events and check webhook signatures

  • streamFeed(client, options) reads one connection of Stream, with Last-Event-ID resume and the event, condition_id, and min_grade filters.
  • streamFeedResilient() reconnects for you. It gives up after maxReconnects consecutive connections that delivered nothing, which defaults to 10; any delivered frame resets the count.
  • A Retry-After longer than maxRetryAfterMs (60 seconds by default) throws StreamRetryDeferredError with retryAt and lastSeq for you to schedule. The client never reconnects early.
  • A malformed frame, a 2xx that is not SSE, or a frame past maxFrameBytes (1 MiB) throws StreamProtocolError with reason, lastSeq, and frameId. The cursor never moves past a bad frame, and the reconnect loop does not retry it.
  • consumeStreamCheckpointed(client, handlers, options) is the consumer for work you cannot afford to drop. It keeps two positions: options.cursor is the last frame that arrived, and options.checkpoint is the last frame you finished handling. The checkpoint advances only after your onEvent and your onCheckpoint write both resolve.
  • If either handler rejects, the checkpoint stays before that event, the connection closes, and the client reconnects from the checkpoint, so the event is replayed rather than skipped. onResync is awaited too, so an interrupted refresh commits nothing.
  • After maxHandlerRetries consecutive failures on one seq (3 by default), it throws StreamHandlerFailedError with seq, stage, attempts, checkpoint, and replayFrom. Delivery is at least once, so deduplicate on seq or make the side effect idempotent.
  • verifySignature(input) checks x-0xinsider-signature against the raw body, with the 300-second tolerance and a constant-time compare. It splits the header on commas and accepts any candidate, so a staged secret rotation verifies. It returns false for a bad signature or timestamp, and throws for an empty secret or a NaN, infinite, or negative toleranceSeconds.
  • parseWebhookEvent(body) returns a typed event. See Webhooks.

What it does not do

  • Place an order or hold a wallet key. It reads data and manages the webhooks on your own account.
  • Retry a 400, 401, 402, 403, 404, 409, 500, a write with no idempotency key, a write outside the 8 keyed operations, its own timeout, or your own abort.
  • Wait out a Retry-After over 60 seconds, on a request or on the stream. It throws instead, for you to schedule from retryAfterSeconds, retryAt, or StreamRetryDeferredError.retryAt.
  • Sleep until retryAt. On pick_not_released that can be 13 to 14 hours out.
  • Apply timeoutMs to the stream. A stream connection has no deadline.