@neuraldraft/sdk
v0.3.4
Published
Official TypeScript SDK for the Neural Draft Project API — typed helpers for brand, content, blog, images, galleries, products, booking, and async jobs.
Maintainers
Readme
@neuraldraft/sdk
Official TypeScript SDK for the Neural Draft Project API. Typed helpers for brand, content, blog, images, products, booking, and async jobs.
A small, zero-dependency, typed client over the Neural Draft v1 REST API. Wraps the same auth, pagination, error, and resource conventions documented in the OpenAPI spec.
Demo
Five lines of TypeScript: read brand, list products, create a published blog post.

Install
npm install @neuraldraft/sdkRequires Node 20+ (uses the built-in fetch). Also works in any modern runtime that has fetch and AbortController in scope (Bun, Deno via npm: import, edge workers).
Quickstart
Get a project API key from the dashboard. Use a test-mode key (ndsk_test_…) in development; live keys (ndsk_live_…) bill against your project's credits.
import { NeuralDraftClient } from "@neuraldraft/sdk";
const nd = new NeuralDraftClient({
apiKey: process.env.NEURALDRAFT_API_KEY!,
});
const project = await nd.projects.me();
console.log(`Hello, ${project.name}.`);By convention the API key lives in NEURALDRAFT_API_KEY. Never hard-code it; never commit it.
Configuration
new NeuralDraftClient({
apiKey: "ndsk_live_...", // required
apiUrl: "https://api.neuraldraft.io/v1", // optional, this is the default
userAgent: "my-app/1.0.0", // optional, identifies your app in API logs
timeout: 30_000, // optional, ms; defaults to no timeout
fetch: customFetch, // optional, inject for testing or proxying
});API surface
The client groups methods into resource namespaces that match the API tags.
projects
const project = await nd.projects.me(); // GET /projects/mebrand
const brand = await nd.brand.get(); // GET /brand
await nd.brand.update({ colors: { primary: { hex: "#2A4A3C" } } }); // PATCH /brandcontent
// Read a single key
const v = await nd.content.get("hero.headline", { lang: "fr" });
console.log(v.value, v.all_locales);
// Upsert a value (creates the key if it doesn't exist). Charges 1 credit.
await nd.content.set("hero.headline", "Welcome", "en");
// Bulk-create translation keys with default values. 409 (key exists) is
// treated as "skipped" rather than failing the batch.
const r = await nd.content.bulkCreate(
{ "hero.headline": "Find your calm", "hero.cta": "Get started" },
"en",
);
console.log(r.created, r.skipped_existing);
// Async translate one key into many target locales. Charges 7 credits per
// language. Returns a JobReference — poll via nd.jobs.poll(job.id).
const job = await nd.content.translate("hero.headline", ["fr", "de"]);components
// Register editable HTML. data-translate="..." attributes become content keys;
// data-image-key="..." attributes become image slots.
const cmp = await nd.components.register({
html: '<section><h1 data-translate="hero.headline">Hello</h1></section>',
intent: "marketing_hero",
page_slug: "home",
});
console.log(cmp.id, cmp.editor_url);
const list = await nd.components.list({ page_slug: "home", page_size: 50 });
const one = await nd.components.get("cmp_2Ngd9KqLmRpW");blogPosts
// Manual draft (synchronous, 0 credits — counts against your post quota)
const post = await nd.blogPosts.create({
title: "5-minute breathwork",
content: "<p>Hi.</p>",
language_code: "en",
});
// AI generation (async — returns a Job; costs 60 credits)
const job = await nd.blogPosts.generateAi({
topic: "5-minute breathwork for anxious mornings",
word_count: 1200,
primary_keyword: "morning breathwork",
translate_to_all: true,
});
const finished = await nd.jobs.poll(job.id);
console.log("Generated:", finished.result);
// Translate an existing post to additional languages (7 credits per language)
const tjob = await nd.blogPosts.translate(post.id, ["de", "fr"]);
// Read & list
const posts = await nd.blogPosts.list({ status: "published", lang: "en" });
const one = await nd.blogPosts.get("5-minute-breathwork-for-anxious-mornings");images
// Async AI generation — costs 32 credits per image
const job = await nd.images.generate({
prompt: "Serene yoga studio at dawn",
aspect_ratio: "16:9",
key: "hero.background",
});
const finished = await nd.jobs.poll(job.id);
const { url, key } = finished.result as { url: string; key: string };
// Synchronous URL swap — 1 credit
await nd.images.replace("hero.background", { url: "https://cdn.example/img.jpg" });
// Direct file upload (multipart) — 1 credit
await nd.images.upload("logo", fileOrBlob, { filename: "logo.svg" });products
const list = await nd.products.list({ status: "active" });
const p = await nd.products.get(42);
const created = await nd.products.create({
name: "Cork yoga block (pair)",
price: 2999,
currency: "gbp",
type: "physical",
status: "active",
});
await nd.products.update(created.id, { price: 2799 });booking
const services = await nd.booking.listServices({ status: "active" });
const svc = await nd.booking.getService(12);
// Resolves an embeddable widget snippet for a service. Throws ApiError(404)
// if the service id is unknown. Both tenant id and service id are required —
// the widget script lives at /v1/widgets/booking/{tenant_id}/{service_id}.js.
const me = await nd.projects.me();
const widget = await nd.booking.getWidget(me.id, 12);
console.log(widget.embed_html); // <script src="..." async data-neuraldraft-booking="12"></script>jobs
// One-shot read
const job = await nd.jobs.get("job_2Ngd9KqLmRpW");
// Poll until terminal (completed | failed | cancelled). Throws on timeout.
const finished = await nd.jobs.poll(job.id, {
intervalMs: 1500,
timeoutMs: 5 * 60_000,
});
if (finished.status === "failed") {
console.error(finished.error);
}
// Cancel an in-flight job
await nd.jobs.cancel(job.id);Central login: workspace picker
When an email is registered against more than one workspace, the central
login form needs to know which one to log into. NeuralDraftClient.tenantsForEmail
hits the central host (https://app.neuraldraft.io) without an API key and
returns the candidate workspaces:
const { tenants } = await NeuralDraftClient.tenantsForEmail("[email protected]");
for (const t of tenants) {
console.log(t.id, t.name, t.domain);
}Error handling
Every method throws ApiError on non-2xx responses or transport failures.
import { ApiError } from "@neuraldraft/sdk";
try {
await nd.blogPosts.generateAi({ topic: "..." });
} catch (err) {
if (err instanceof ApiError) {
if (err.status === 401) {
// Invalid API key — check your env config
} else if (err.status === 402) {
// Out of credits — direct user to top up at /billing
} else if (err.status === 422) {
// Validation: err.body is the API's RFC 7807 JSON; parse for `errors`
} else if (err.status === 429) {
// Rate-limited (60 req/min default) — back off and retry
} else {
throw err;
}
}
}Branch on the API's stable machine code (in the JSON body's code field), not on the title string:
const body = JSON.parse(err.body) as { code?: string };
if (body.code === "out_of_credits") { /* … */ }status: 0 indicates a transport failure (DNS, connection refused, timeout). path carries the API path that produced the error, useful for logs.
Pagination
List endpoints return Paginated<T>:
interface Paginated<T> {
data: T[];
meta: { page: number; page_size: number; total: number };
}Iterate page by page yourself; the SDK is intentionally low-level.
Async jobs
The API returns a Job reference for any operation that takes more than a couple of seconds (blog AI generation, image generation, batch translation). Jobs have a stable lifecycle:
pending → processing → completed | failed | cancelledclient.jobs.poll(id) calls GET /jobs/{id} repeatedly until the job reaches a terminal state. failed and cancelled resolve with the job (don't throw) — inspect job.status and job.error to decide what to do next.
Versioning
This SDK targets the v1 stable surface of the Neural Draft API. Major version bumps mirror breaking API changes; minor versions add new methods or resources. Patch versions are bug fixes only.
Local development
npm install
npm run lint # tsc --noEmit
npm test
npm run build # → dist/To run against a local API:
NEURALDRAFT_API_KEY=ndsk_test_xxx \
NEURALDRAFT_API_URL=http://localhost/v1 \
npx tsx scripts/smoke.tsLinks
- Documentation
- OpenAPI spec
- MCP server (
@neuraldraft/mcp) — for AI coding tools (Claude Code, Cursor, etc.) - Issues
License
MIT.
