Authentication

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

terminal
curl https://www.nodedata.ai/api/v1/models \
-H "Authorization: Bearer $NODE_DATA_API_KEY"

Key format

anatomy
nd_live_a1b2c3d4e5f6g7h8i9j0k1l2
└──┬───┘└──────────┬───────────┘
│ └── secret body (never stored in plaintext)
└── public prefix: first 12 chars, used to look the key up

The 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

PrefixModeUse for
nd_test_…testLocal development, CI, integration tests.
nd_live_…liveProduction. 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.

guard.ts
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 log

Scopes

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.

ScopeGrants
models:readList and read published listings.
models:uploadPublish new models to the marketplace.
datasets:readList and read published datasets.
datasets:uploadPublish new datasets.
listings:readRead your own listings, including drafts.
listings:writeEdit, reprice, and unpublish your listings.
payouts:readRead completed sales and the creator split.
deploy:writeCreate deployments from a listing.
webhooks:writeCreate, edit, and delete webhook endpoints.
inference:runCall the paid inference API. Premium keys only.
factory:runRun AI Factory jobs. Each generation spends a Factory credit.

Scope errors are 403, not 401

A 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.

the difference
# strips the header on the redirect → missing_credentials
curl -L https://nodedata.ai/api/v1/me -H "Authorization: Bearer $KEY"
# reaches the API with credentials intact
curl 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.

handle-scope.ts
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.