nodedata — Python SDK
Official Python client for the Node Data API: search and download robotics/physical-AI models and datasets, publish your own, read your payouts, subscribe to webhooks, and run Node Data's paid inference.
Zero runtime dependencies. Standard library only, so it installs onto a Jetson next to whatever pinned torch/numpy stack is already there.
pip install nodedata
pip install -e ".[dev]" # from this directory, for development
Requires Python 3.9+.
Quick start
Create a key at /dashboard/api-keys
(nd_test_… or nd_live_…) and export it:
export NODE_DATA_API_KEY=nd_live_...
from nodedata import NodeData
nd = NodeData() # reads NODE_DATA_API_KEY
me = nd.me()
print(me.name, me.key.mode, me.key.scopes)
# Search the marketplace
for listing in nd.models.iter_all(q="grasp", type="dataset", max_items=50):
print(listing.slug, listing.price.dollars, listing.url)
# Download something you own or that's free
path = nd.models.download("acme-panda-grasp-100k", "./cache")
# Publish a build artifact — uploads the file, then creates the listing
listing = nd.models.publish(
"dist/grasp-policy-v2.onnx",
title="Grasp policy v2",
description="Trained on 100k teleop episodes.",
type="policy",
price_cents=4900, # 0 for free; paid minimum is 100
frameworks=["pytorch"],
)
print(listing.url)
What's covered
| Area | Calls |
|---|---|
| Account | nd.me() |
| Listings | nd.models.list() · iter_all() · retrieve() · create() · update() · unpublish() |
| Files | nd.models.download() · download_url() · nd.uploads.upload_file() · create() · confirm() · rules() |
| Publish | nd.models.publish() (upload + create in one call) |
| Payouts | nd.payouts.retrieve() |
| Usage | nd.usage.retrieve(days=30) |
| Webhooks | nd.webhooks.list() · create() · retrieve() · update() · delete() · deliveries() · event_types() |
| Inference | nd.inference.models() · chat() · stream() · stream_text() · ask() |
| Anything newer | nd.request("GET", "/some/new/endpoint") |
nd.datasets is an alias of nd.models — one endpoint serves both.
Every returned object keeps the raw response in .raw, so a field the API adds
after this release is still readable without upgrading.
Pagination
list() returns one Page; iter_all() walks every page lazily.
page = nd.models.list(limit=100)
print(len(page), page.has_more, page.next_cursor)
for listing in nd.models.iter_all(type="dataset"): # follows cursors
...
Inference
Requires a premium key with the inference:run scope. The endpoint is
OpenAI-compatible, so openai pointed at https://www.nodedata.ai/api/v1 works
too — these helpers just avoid the dependency.
for model in nd.inference.models():
print(model.id, model.context_window, model.pricing.input_per_1m)
print(nd.inference.ask("Summarise this dataset card.", model="node-reason-1"))
for text in nd.inference.stream_text(
model="node-reason-1",
messages=[{"role": "user", "content": "Explain grasp policies"}],
):
print(text, end="", flush=True)
The final streamed chunk carries usage and no text — that's the frame to log
spend against.
Webhooks
Verify with the raw request body. Re-serialising parsed JSON changes bytes, and the signature won't match.
from nodedata import verify_webhook, WebhookVerificationError
endpoint = nd.webhooks.create(
"https://example.com/hooks/nodedata",
events=["listing.purchased", "payout.paid"],
)
print(endpoint.secret) # returned on create only — store it now
# in your handler
try:
event = verify_webhook(request.body, request.headers["nd-signature"], SECRET)
except WebhookVerificationError:
return 400
verify_webhook enforces HMAC-SHA256 with a constant-time compare and a 300s
replay window, matching the server.
Errors
Everything raises a subclass of NodeDataError:
| Exception | Status | Typical cause |
|---|---|---|
InvalidRequestError |
400 | validation failed |
AuthenticationError |
401 | missing, revoked or expired key |
PaymentRequiredError |
402 | premium_required, purchase_required, unpaid key |
PermissionDeniedError |
403 | key lacks the scope |
NotFoundError |
404 | no such object — or not yours |
RateLimitError |
429 | carries retry_after |
MaintenanceError |
503 | platform deliberately closed; carries retry_after |
ServerError |
5xx | API failure |
APIConnectionError / APITimeoutError |
— | never reached the API |
Branch on exc.code (purchase_required, scope_required, price_too_low, …)
rather than the message text.
from nodedata import PaymentRequiredError
try:
nd.models.download("some-paid-asset", "./cache")
except PaymentRequiredError as exc:
if exc.code == "purchase_required":
print(f"costs ${exc.body['price_cents'] / 100:.2f}")
Retries
Rate limits and transient failures are retried twice by default with jittered
backoff, honouring Retry-After. POST is only retried on a 429 — the server
rejected it before doing any work — so a publish can never double-apply.
nd = NodeData(timeout=30, max_retries=5)
CLI
Installing the package also installs a nodedata command:
nodedata whoami
nodedata search grasp --type dataset
nodedata download acme-panda-grasp-100k -o ./cache
nodedata publish dist/policy.onnx --title "Grasp policy" --description "..." --type policy
nodedata payouts
nodedata usage --days 7
nodedata ask "Explain grasp policies" --model node-reason-1
Configuration
| Env var | Purpose |
|---|---|
NODE_DATA_API_KEY |
API key used when none is passed |
NODE_DATA_BASE_URL |
Override the API base URL (preview deploys, self-host) |
Certificates
Because this SDK uses urllib rather than bundling certifi, TLS trust comes
from the system store. Some Python builds — notably the macOS python.org
installers — ship an empty one, which fails every HTTPS request with
CERTIFICATE_VERIFY_FAILED. Three fixes, in order of ease:
pip install certifi # used automatically if present
open "/Applications/Python 3.x/Install Certificates.command"
import ssl
nd = NodeData(ssl_context=ssl.create_default_context(cafile="/path/to/ca.pem"))
The SDK detects this specific failure and says so, rather than reporting it as an API outage.
Tests
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest # hermetic; runs a local HTTP server
NODE_DATA_LIVE_KEY=nd_live_... .venv/bin/python -m pytest tests/test_live.py -v
The suite talks to a real socket rather than a patched transport, so retries, SSE framing and streamed downloads are exercised as they run in production. The live suite is read-only.
Versioning
Minor releases may add response fields. Pin in CI and bump deliberately.
MIT © Node, Inc.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nodedata-0.1.2.tar.gz.
File metadata
- Download URL: nodedata-0.1.2.tar.gz
- Upload date:
- Size: 38.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb1133f571d25ef3b00129a285f4a4ff2b9bbece1995049602bc262d31337d1c
|
|
| MD5 |
437500dc225211dc16829245d592664c
|
|
| BLAKE2b-256 |
285ad280a3b78e44cbc4c5b8f523d785a6f979ffdeb8e1358372e8b8ff8250e6
|
File details
Details for the file nodedata-0.1.2-py3-none-any.whl.
File metadata
- Download URL: nodedata-0.1.2-py3-none-any.whl
- Upload date:
- Size: 34.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2b499b2e158999819b519197e27ad8e13638fe25f09f5f122dd0e159a9b7258
|
|
| MD5 |
6431c124d318f1e79e3a91f9871e9568
|
|
| BLAKE2b-256 |
47ab7ca97ef02561544bc4afb1802fdf8ce10e38ace78c8744a5aa499439da51
|