Skip to main content
Neural Draft uses a single auth model for the v1 Project API: a long-lived project key passed as a Bearer token. That’s it — no OAuth dance, no short-lived JWTs, no per-user signing. One key per project per environment.

Key format

Every key is prefixed with environment and scoped to a single project:
ndsk_live_2NgcaXxFqLPo7K3vR8zHwT5sE9bM1nDc
└─┬─┘ └─┬┘ └────────────────┬─────────────┘
  │     │                   │
  │     │                   └── 32-char random secret (base62)
  │     └────────────────────── environment: live | test
  └──────────────────────────── product prefix: ndsk = "Neural Draft Secret Key"
Use the prefix to spot-check intent at a glance: a ndsk_test_ key in your production env is a clear bug. Our SDKs and dashboards both surface the prefix so it’s hard to confuse the two.

Where to get a key

1

Open Settings → API keys

From the dashboard at app.neuraldraft.io.
2

Click Create key

Pick a memorable name (e.g. vercel-prod, lovable-preview, claude-code-laptop). Future-you reading the audit log will thank present-you.
3

Choose the scopes

Default is read+write across CMS, blog and social. Tighten to least privilege when the key has a known job — for example, a static-site build only needs content:read and blog:read.
4

Copy it once

Keys are shown once. Paste straight into your secret manager. We store only a hash — we cannot recover a lost key.

Sending the key

A standard Bearer token in the Authorization header. No query-param fallback, no body field. HTTPS is required.
curl https://api.neuraldraft.io/v1/projects/me \
  -H "Authorization: Bearer ndsk_live_..."

Scopes

Scopes are wildcard-prefixed. A key with content:* can do every action under content; * is full access (use it carefully — only for trusted backend services).
ScopeWhat it covers
*Full access. Equivalent to all scopes below.
project:readRead project metadata, usage, key list.
project:writeUpdate project (e.g. webhook URL, target languages).
project:adminMint/revoke API keys, change billing.
content:readRead translation keys, components, pages.
content:writeCreate/update/delete translations and components.
brand:readRead brand context (voice, colors, fonts, audience).
brand:writePatch brand context, upload logos.
images:readRead media library and resolved image URLs.
images:writeUpload, generate, replace images.
blog:readRead posts, categories, tags, translations.
blog:writeCreate/update/delete posts, schedule, publish.
social:readRead social posts and connected accounts.
social:writeCreate/update/delete social posts.
social:publishPublish a social post immediately to a connected platform.
social:connectInitiate OAuth flow, manage connected accounts.
booking:readRead services, availability, bookings.
booking:writeCreate/update services, availability, bookings.
booking:adminUpdate booking settings (timezone, policies, reminders).
commerce:readRead products, variants, categories, orders.
commerce:writeCreate/update/delete products, variants, categories, orders.
commerce:adminRefunds, Stripe Connect onboarding, manual orders.
webhooks:readList webhook endpoints and deliveries.
webhooks:writeCreate/update/delete webhook endpoints.
jobs:readRead job status (returned by all async ops).
A 403 Forbidden with code: "forbidden" is returned when the key lacks the required scope; the response includes the scope you’d need.
{
  "type": "https://api.neuraldraft.io/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "code": "forbidden",
  "detail": "This API key does not have the `commerce:write` scope.",
  "instance": "req_2Nh4PqRsTuVw"
}

Common errors

StatuscodeWhen
401unauthorizedMissing, malformed, expired or revoked key.
403forbiddenKey is valid but lacks the required scope.
402insufficient_creditsProject balance too low for the operation. Top up or upgrade.
429rate_limitedPer-project window exhausted. See Retry-After.
503service_unavailableBackend dependency down. Retry with backoff.
Full error catalog: errors.

Best practices

Mint separate keys for production, staging, preview, and each developer laptop. The audit log shows last-used timestamps per key — anomalies become obvious.
Mint a new key, deploy with both old and new accepted (we accept multiple valid keys per project), then revoke the old one once nothing 401s. The dashboard has a one-click rotation flow that automates the overlap window.
Use environment variables. On Vercel/Netlify/Render, the encrypted env UI is the right place. For local dev, a .env file in .gitignore and dotenv-style loading. Our CLI refuses to write a key to disk outside of a recognised secret manager.
A static-site builder needs content:read and blog:read, nothing more. A scheduler-only service needs social:write and social:publish. Keys leak; small blast radius is the only mitigation that works at scale.
Test keys are fine to share with contractors, paste into Lovable previews, or include in a public GitHub issue when reproducing a bug. They have a separate credit pool that resets monthly and never bills.
Every key has a last_used_at and a per-IP usage stripe in the dashboard. Suspicious activity = a chance to rotate before damage compounds. Suspect leakage? Revoke immediately; it takes effect within 60 seconds globally.

Revoking a key

A revoked key starts returning 401 unauthorized within ~60 seconds globally. Active jobs queued against the key continue to completion (they were authorised at submit time); new requests fail.
curl -X DELETE https://api.neuraldraft.io/v1/api-keys/key_2NgcaXxFqLPo \
  -H "Authorization: Bearer $NEURALDRAFT_API_KEY"
Revocation is permanent; the key string can never be re-issued.

Multiple keys per project

A project can hold up to 20 active keys. Use this for environment separation and friction-free rotation:
NameScopesWhere it lives
prod-server* (or write scopes)Server-side, secret manager
prod-buildercontent:read, blog:readCI build pipeline
prod-mcp*Claude Code on dev laptops
staging-server*Staging deployments
lovable-previewcontent:*, images:*Lovable’s hosted preview env
A 21st create attempt returns 409 Conflict with code: "key_limit_reached" — revoke an unused one first.

Central login (multi-workspace accounts)

If a single email is registered against more than one project (common for agencies and people running multiple sandboxes), the central login flow at app.neuraldraft.io needs to know which workspace to drop the user into. Two endpoints power this — both live on the central host (app.<central_domain>), neither is a per-tenant API surface, and neither uses an API key: they’re for the dashboard / admin SPA, not for server-to-server traffic.
The v1 Project API documented above is unchanged — these endpoints are a separate auth path the dashboard uses to issue Sanctum tokens. Skip this section if you’re only consuming /v1/*.

GET /central/api/tenants-for-email

Returns every workspace the email is registered against. Always 200; the response is constant-time across known/unknown emails to defeat enumeration. An unknown email returns { tenants: [] }.
curl "https://app.neuraldraft.io/central/api/tenants-for-email?email=jane@example.com"
{
  "tenants": [
    { "id": 42, "name": "Acme",        "domain": "acme.neuraldraft.io" },
    { "id": 87, "name": "Acme Studio", "domain": "studio.neuraldraft.io" }
  ]
}
If tenants.length > 1, render a workspace picker; otherwise log straight in.

POST /central/api/login

Email + password authentication that issues a Sanctum token. The optional tenant_id disambiguates when the email is registered to multiple workspaces — pass it whenever the picker has a selection so iteration across candidates can’t pick the wrong one (e.g. password rotated on one tenant but a stale row still points at another).
# Multi-workspace flow: explicit tenant_id from the picker
curl -X POST https://app.neuraldraft.io/central/api/login \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","password":"...","tenant_id":42}'
{
  "token": "1|abcdef...",
  "tenant": { "id": 42, "name": "Acme", "domain": "acme.neuraldraft.io" },
  "user":   { "id": 7, "name": "Jane Doe", "email": "jane@example.com", "created_at": "..." }
}
tenant_id is optional — if you omit it, the server iterates each candidate workspace and uses the first one whose password verifies. Iteration is case-insensitive on email. A wrong-password attempt always returns 422 Unprocessable Entity with auth.failed, regardless of whether the email exists.

Where Sanctum and OAuth fit in

The v1 Project API documented here is API-key only. The separate Admin API — used by white-label dashboards and agencies managing multiple projects — uses scoped Sanctum tokens (tenants:read, posts:write, …) and is documented in its own reference. If you’re building your own admin UI on top of Neural Draft, that’s the auth model you want; for everything else, stay here.