Keys, modes, and scopes
Every request carries a bearer token. There are no sessions, no OAuth dance, and no refresh tokens — a key is either valid or it isn't.
The header
curl https://www.nodedata.ai/api/v1/models \ -H "Authorization: Bearer $NODE_DATA_API_KEY"Key format
nd_live_a1b2c3d4e5f6g7h8i9j0k1l2└──┬───┘└──────────┬───────────┘ │ └── secret body (never stored in plaintext) └── public prefix: first 12 chars, used to look the key upThe first 12 characters are a public identifier. The remainder is secret and only ever stored as a SHA-256 hash, so:
- The dashboard can show you which key is which, safely.
- Logging the prefix is fine. Logging the whole key is not.
- A lost key cannot be recovered, only revoked and replaced. That's the design working.
Test and live modes
| Prefix | Mode | Use for |
|---|---|---|
nd_test_… | test | Local development, CI, integration tests. |
nd_live_… | live | Production. Real listings, real money, real payouts. |
Because the mode is in the prefix, you can detect it without a network call — useful for a startup assertion that catches the worst configuration mistake there is.
const nd = new NodeData({ apiKey: process.env.NODE_DATA_API_KEY }); if (process.env.NODE_ENV === "production" && nd.mode !== "live") { throw new Error("Refusing to boot production against a test key.");} console.log(nd.maskedKey); // nd_live_a1b2•••••••••••••••••••• — safe to logScopes
Scopes are chosen when the key is created and cannot be changed afterwards. A key with an empty scope list has unrestricted access — convenient, and worth avoiding.
| Scope | Grants |
|---|---|
models:read | List and read published listings. |
models:upload | Publish new models to the marketplace. |
datasets:read | List and read published datasets. |
datasets:upload | Publish new datasets. |
listings:read | Read your own listings, including drafts. |
listings:write | Edit, reprice, and unpublish your listings. |
payouts:read | Read completed sales and the creator split. |
deploy:write | Create deployments from a listing. |
webhooks:write | Create, edit, and delete webhook endpoints. |
inference:run | Call the paid inference API. Premium keys only. |
factory:run | Run AI Factory jobs. Each generation spends a Factory credit. |
Scope errors are 403, not 401
scope_required error means the key authenticated fine and simply wasn't granted that permission. Retrying won't help — create a new key with the right scopes.Use the www host
The canonical API host is https://www.nodedata.ai/api/v1. The bare domain answers 308 and redirects there — and fetch strips the Authorization header across a cross-origin redirect, which the bare-to-www hop counts as.
The symptom is distinctive: you set the header correctly and still get missing_credentials rather than invalid_key. The API never saw your credentials at all.
# strips the header on the redirect → missing_credentialscurl -L https://nodedata.ai/api/v1/me -H "Authorization: Bearer $KEY" # reaches the API with credentials intactcurl https://www.nodedata.ai/api/v1/me -H "Authorization: Bearer $KEY"cURL only forgets the header when following redirects with -L; browser and Node fetch follow redirects by default, so they always drop it. The SDK defaults to the www host so this cannot happen to you.
import { PermissionError } from "@node-data/sdk"; try { await nd.payouts.retrieve();} catch (err) { if (err instanceof PermissionError) { console.error(`This key needs the ${err.requiredScope} scope.`); return; } throw err;}Premium keys and 402
The inference endpoints require a premium key with inference:run. A premium key that hasn't completed its one-time activation checkout returns 402 payment_required — distinct from 401 and 403 on purpose, because the credential is real and correctly scoped, just not activated. Finish checkout at the keys dashboard and the same key starts working.
Handling keys without leaking them
- One key per service. Revoking then affects one thing, not four.
- Least privilege. A read-only key in a reporting job cannot publish a listing, whatever the bug.
- Never in the client. The SDK throws if you construct it with a live key in a browser. Proxy through your own server, where you can also add per-user rate limiting.
- Rotate on a schedule and immediately on any suspected exposure. Assume a pushed key is compromised — public repos are scraped continuously.
- Log prefixes, not keys. Error reporters capture request headers more often than people expect.
Verifying a key you just found
The key inspector parses format and mode entirely locally, and can optionally call GET /me to report the live scopes.