SDK reference
Every namespace and method on the client. Signatures are copied from sdk/src, not paraphrased. Start with the overview if you haven't installed it yet.
import { NodeData, // webhook helpers verifyRequest, verifyWebhook, signPayload, SIGNATURE_HEADER, // errors ApiError, AuthenticationError, BadRequestError, ConflictError, ConnectionError, NodeDataError, NotFoundError, PaymentRequiredError, PermissionError, RateLimitError, ServerError, TimeoutError, WebhookVerificationError, // values SCOPES, WEBHOOK_EVENT_TYPES, DEFAULT_BASE_URL, SDK_VERSION, // pagination Page,} from "@node-data/sdk";NodeData
new NodeData(options?)
The client. Reads NODE_DATA_API_KEY and NODE_DATA_BASE_URL from the environment when the corresponding options are omitted.
nd.modeGetter: "test", "live", or "unknown", derived from the key prefix without a network call.
→ KeyMode | "unknown"
nd.maskedKeyGetter: the public prefix plus dots. Safe to log.
→ string
nd.baseUrlGetter: the API root this client is pointed at.
→ string
nd.ping()One round trip that confirms the key works and reports the account slug and scopes. Never throws — failures come back as { ok: false, error }.
→ Promise<{ ok, latencyMs, account?, scopes?, error? }>
const health = await nd.ping();if (!health.ok) process.exit(1);nd.request(method, path, { query?, body?, options? })Escape hatch for endpoints the SDK doesn't model yet. Same auth, retries, and error mapping as the typed methods.
→ Promise<T>
const data = await nd.request<{ items: unknown[] }>( "GET", "/some/new/endpoint", { query: { limit: 10 } },);nd.models
Marketplace listings
Models, policies, workflows, and datasets — one underlying resource, distinguished by `type`.
list(params?, options?)models:readA page of published listings. Filter with q, type, category; paginate with limit and cursor. The returned Page is async-iterable over every subsequent page.
→ Promise<Page<Asset>>
const page = await nd.models.list({ q: "grasping", limit: 50 });for await (const asset of page) console.log(asset.slug);iterate(params?, options?)models:readThe same walk as an async generator, when you'd rather not hold the first Page.
→ AsyncGenerator<Asset>
retrieve(idOrSlug, options?)models:readOne published listing by id or URL slug. Unpublished listings are not addressable.
→ Promise<Asset>
create(params, options?)models:uploadPublishes a listing. price_cents is integer cents — 0 for free, ≥100 for paid. Attach a file by uploading out of band first and passing storage_path.
→ Promise<Asset>
const asset = await nd.models.create({ title: "Bin-Picking Policy v2", description: "Diffusion policy trained on 40k grasps.", type: "policy", price_cents: 4900,});update(idOrSlug, params, options?)listings:writeEdits metadata, price, or publish state on a listing you own. Another account's listing reads as 404, not 403.
→ Promise<Asset>
del(idOrSlug, options?)listings:writeUnpublishes a listing. Purchase history and buyer download rights are preserved.
→ Promise<{ id, deleted }>
nd.datasets
Dataset listings
A thin wrapper over models that defaults `type` to "dataset". Same shapes, dataset scopes.
list(params?, options?)datasets:readPublished datasets, cursor-paginated.
→ Promise<Page<Asset>>
retrieve(idOrSlug, options?)datasets:readOne dataset by id or slug.
→ Promise<Asset>
create(params, options?)datasets:uploadPublishes a dataset — `type` is set for you.
→ Promise<Asset>
nd.account
Account & usage
Who this key belongs to, and what it has spent.
retrieve(options?)The authenticated account plus the key's mode and scopes. An empty scopes array means unrestricted.
→ Promise<Account>
usage({ days? }, options?)Metered inference spend over a window (1–365 days, default 30), rolled up and per model. Costs come back in USD micros and dollars.
→ Promise<Usage>
const usage = await nd.account.usage({ days: 7 });console.log(usage.summary.cost_usd);nd.payouts
Sales & payouts
Completed sales for listings you authored, and the 25/75 split.
retrieve({ limit? }, options?)payouts:readSales plus an all-time summary. payouts_paused distinguishes 'nothing owed' from 'owed but not moving' — balances keep accruing while it's true.
→ Promise<Payouts>
const payouts = await nd.payouts.retrieve({ limit: 100 });console.log(payouts.summary.net_cents / 100, "USD");nd.webhookEndpoints
Webhook endpoints
Manage delivery targets. Every method needs webhooks:write; secrets are returned only on create.
list(options?)webhooks:writeAll endpoints on the account. Never includes secrets.
→ Promise<WebhookEndpoint[]>
create(params, options?)webhooks:writeRegisters an endpoint and returns its signing secret exactly once. Unknown event types are silently dropped, so read it back to confirm.
→ Promise<WebhookEndpoint>
const endpoint = await nd.webhookEndpoints.create({ url: "https://example.com/hooks", events: ["listing.purchased"],});await store.set("secret", endpoint.secret!); // shown onceretrieve(id, options?)webhooks:writeOne endpoint's current configuration.
→ Promise<WebhookEndpoint>
update(id, params, options?)webhooks:writeChange the URL, replace the subscription list, or set enabled: false to pause without losing history.
→ Promise<WebhookEndpoint>
del(id, options?)webhooks:writePermanently removes the endpoint and stops all deliveries.
→ Promise<{ id, deleted }>
deliveries(id, { limit? }, options?)webhooks:writeRecent delivery attempts with response status and attempt count. First place to look when a handler goes quiet.
→ Promise<WebhookDelivery[]>
nd.inference
Metered inference
OpenAI-compatible completions. Requires a premium key with inference:run.
models(options?)The Node model catalog with context windows and live per-token pricing. Fetch this rather than hardcoding prices.
→ Promise<InferenceModel[]>
create(params, options?)inference:runOne buffered completion. Always set max_tokens — it's the only hard ceiling on a runaway generation.
→ Promise<ChatCompletion>
stream(params, options?)inference:runStreams parsed SSE chunks. Never retried mid-flight, and given a long default timeout.
→ AsyncGenerator<ChatCompletionChunk>
for await (const chunk of nd.inference.stream({ model, messages })) { process.stdout.write(chunk.choices[0]?.delta.content ?? "");}streamToText(params, { onToken? })inference:runCollects a stream into the full assistant message while optionally handing you each token as it arrives.
→ Promise<string>
Webhook helpers
Top-level exports · Web Crypto only
These use Web Crypto rather than Node built-ins, so the same code runs on Node 18+, Bun, Deno, Workers, and Vercel Functions. Verification enforces a timestamp tolerance to neutralize replays and accepts multiple v1 values so secret rotation doesn't drop deliveries.
verifyRequest(request, secret, { tolerance? })Verifies a standard Request and returns the parsed event. Reads the raw body itself, so you can't accidentally verify re-serialized JSON.
→ Promise<WebhookEvent<T>>
import { verifyRequest } from "@node-data/sdk"; export async function POST(req: Request) { const event = await verifyRequest(req, process.env.ND_WEBHOOK_SECRET!); return new Response(null, { status: 204 });}verifyWebhook({ payload, signature, secret, tolerance?, now? })The lower-level form, for frameworks that hand you the body and headers separately. Default tolerance is 300 seconds.
→ Promise<WebhookEvent<T>>
signPayload(payload, secret, timestamp?)Produces an nd-signature header value. Useful for testing your own handler without waiting on a real delivery.
→ Promise<string>
SIGNATURE_HEADERThe header name constant: "nd-signature".
→ string
Error classes
Catch the specific case you can handle and re-throw the rest. catch gives you unknown under modern configs, so instanceof is how you get typed access.
| Class | Status | Notes |
|---|---|---|
| NodeDataError | — | Base class for everything the SDK throws. |
| ApiError | any | Base for non-2xx. Carries status, code, requestId, body. |
| BadRequestError | 400 | Validation or malformed request. |
| AuthenticationError | 401 | Missing, malformed, revoked, or expired key. |
| PaymentRequiredError | 402 | Premium key not yet activated. |
| PermissionError | 403 | Valid key, missing scope. Exposes requiredScope. |
| NotFoundError | 404 | No such resource, or not yours. |
| ConflictError | 409 | Conflicting state. Retryable. |
| RateLimitError | 429 | Too fast. Exposes retryAfter in seconds. |
| ServerError | 5xx | Platform-side failure. Retryable. |
| ConnectionError | — | No HTTP response at all: DNS, offline, abort. |
| TimeoutError | — | Exceeded the configured timeout. Subclass of ConnectionError. |
| WebhookVerificationError | — | Signature failed. code is malformed_signature, timestamp_out_of_tolerance, or signature_mismatch. |
Options
NodeDataOptions
| Option | Default | Notes |
|---|---|---|
| apiKey | process.env.NODE_DATA_API_KEY | Throws if neither is set. |
| baseUrl | https://www.nodedata.ai/api/v1 | Or NODE_DATA_BASE_URL. |
| timeout | 60000 | Milliseconds, per attempt. |
| maxRetries | 2 | 429/5xx/network only. Streams force 0. |
| headers | {} | Merged over the SDK defaults. |
| fetch | globalThis.fetch | Swap in for tests, proxies, or tracing. |
| onRequest | — | Called before every attempt with { method, url, attempt }. |
| dangerouslyAllowBrowser | false | Live keys in a browser throw without it. Trusted internal tools only. |
RequestOptions (per call)
await nd.models.list( { limit: 100 }, { timeout: 5_000, // override the client timeout maxRetries: 0, // fail fast instead of retrying headers: { "x-trace": traceId }, signal: controller.signal, // cancellation },);Page<T>
const page = await nd.models.list(); page.items; // T[] — this page onlypage.hasMore; // booleanpage.nextCursor; // string | nullawait page.next(); // Page<T> | nullawait page.all(500); // T[] — walk with a ceilingfor await (const item of page) { /* every page, lazily */ }Missing an endpoint?
New endpoints ship before SDK methods do. Use nd.request() in the meantime — it goes through the same auth, retry, and error mapping as everything above, so you don't lose anything but types.