@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.
Build from source
The client needs Node.js 18 or later, because it uses the built-infetch, ReadableStream, and node:crypto. It has no runtime dependencies of its own, and it is ESM only.
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 exactvalue 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.
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 outside2xx 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 anidempotencyKey: 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, withLast-Event-IDresume and theevent,condition_id, andmin_gradefilters.streamFeedResilient()reconnects for you. It gives up aftermaxReconnectsconsecutive connections that delivered nothing, which defaults to 10; any delivered frame resets the count.- A
Retry-Afterlonger thanmaxRetryAfterMs(60 seconds by default) throwsStreamRetryDeferredErrorwithretryAtandlastSeqfor you to schedule. The client never reconnects early. - A malformed frame, a
2xxthat is not SSE, or a frame pastmaxFrameBytes(1 MiB) throwsStreamProtocolErrorwithreason,lastSeq, andframeId. 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.cursoris the last frame that arrived, andoptions.checkpointis the last frame you finished handling. The checkpoint advances only after youronEventand youronCheckpointwrite 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.
onResyncis awaited too, so an interrupted refresh commits nothing. - After
maxHandlerRetriesconsecutive failures on oneseq(3 by default), it throwsStreamHandlerFailedErrorwithseq,stage,attempts,checkpoint, andreplayFrom. Delivery is at least once, so deduplicate onseqor make the side effect idempotent. verifySignature(input)checksx-0xinsider-signatureagainst 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 returnsfalsefor a bad signature or timestamp, and throws for an empty secret or aNaN, infinite, or negativetoleranceSeconds.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-Afterover 60 seconds, on a request or on the stream. It throws instead, for you to schedule fromretryAfterSeconds,retryAt, orStreamRetryDeferredError.retryAt. - Sleep until
retryAt. Onpick_not_releasedthat can be 13 to 14 hours out. - Apply
timeoutMsto the stream. A stream connection has no deadline.