@neuraldraft/sdk
v0.4.1
Published
Official TypeScript SDK for the Neural Draft API: site texts, pages, blog, bookings, forms, shop, social posts, webhooks, and several workspaces (agencies) in one client.
Maintainers
Readme
@neuraldraft/sdk
Official TypeScript SDK for the Neural Draft API: the editable texts and pictures of a site, pages, blog, bookings, forms, shop, social posts and webhooks, for one workspace or many.
Neural Draft hosts websites (built in chat, deployed from Claude Code, Codex or Cursor with deploy_site, or hosted anywhere else) and runs what sits behind them: the CMS, blog, bookings, forms, newsletter, shop and social publishing. Every workspace (one per client for agencies) has its own database and its own API key.
Use this SDK from server-side code (a build step, a backend, a script): it talks to the v1 REST API with the workspace's secret key. On the site itself, use the nd.js blocks (<nd-booking>, <nd-form>, <nd-text>…) instead; they need no key. The docs cover both.
Zero dependencies, typed, ESM.
Install
npm install @neuraldraft/sdkNode 20+ (built-in fetch), Bun, Deno (npm: import) or edge runtimes with fetch.
API keys
Each workspace has its own keys: dashboard at app.neuraldraft.io → Settings → API keys. A key only reaches its own workspace. Keep it in an environment variable, never in browser code or git.
One workspace
import { NeuralDraftClient } from "@neuraldraft/sdk";
const nd = NeuralDraftClient.fromEnv(); // reads NEURALDRAFT_API_KEY
// or: new NeuralDraftClient({ apiKey: process.env.NEURALDRAFT_API_KEY! })
const project = await nd.projects.me();
console.log(project.name, project.domain);Options: apiKey (required), apiUrl (default https://api.neuraldraft.io/v1), userAgent, timeout (ms), fetch.
Several workspaces (agencies)
One key per client workspace, in one object. The format is the same as @neuraldraft/mcp 0.4 (NEURALDRAFT_API_KEYS), so one .env serves both.
import { NeuralDraftWorkspaces } from "@neuraldraft/sdk";
// NEURALDRAFT_API_KEYS="villa-lavanda=ndsk_live_…,linden-bakery=ndsk_live_…"
// NEURALDRAFT_WORKSPACE="linden-bakery" (optional: the active one)
const ws = NeuralDraftWorkspaces.fromEnv();
// or: new NeuralDraftWorkspaces({ keys: { "villa-lavanda": "ndsk_live_…", "linden-bakery": "ndsk_live_…" } })
for (const name of ws.names()) {
const { meta } = await ws.get(name).bookings.list({ status: "pending" });
console.log(name, meta.total, "bookings waiting");
}
ws.use("villa-lavanda"); // switch the active workspace
await ws.current.siteTexts.list(); // ws.current = the active clientget(name) returns a normal NeuralDraftClient (created once, then reused). fromEnv falls back to a single NEURALDRAFT_API_KEY as workspace default.
Site texts: what the client can edit
The marked blocks of a site (data-translate="…", <nd-text key="…">, data-image-key="…" in a deployed site, and every text and picture on hosted pages) are the same blocks the client edits in the dashboard (Website → Edit site) or by asking Claude or ChatGPT. Changes are public at once, with no redeploy, and a redeploy never overwrites them.
const { blocks } = await nd.siteTexts.list({ search: "hours" });
// [{ kind: "text", key: "hours", text: "Mon–Sat 7:00–15:00", all_locales: {…} }, { kind: "image", key: "hero", url: "…" }]
const res = await nd.siteTexts.update({
texts: { hours: "Mon–Sat 6:00–14:00" }, // simple HTML is fine: b, i, a, br
images: { hero: "https://cdn.example.com/new-hero.webp" },
lang: "en", // default: the site's default language
});
// { language: "en", changed: ["hours", "hero"], unknown: [] }Only keys that already exist are changed (unknown keys come back in unknown); pass createMissing: true to create them. Built on PUT /content/{key} and PUT /images/{key}. 0 credits.
Lower level, all content keys with every language:
const page1 = await nd.content.list({ lang: "de", search: "hero", page_size: 200 });
const one = await nd.content.get("hero.title", { lang: "fr" });
await nd.content.set("hero.title", "Willkommen", "de");
const job = await nd.content.translate("hero.title", ["fr", "it"]); // 10 credits per languagePages and components
const pages = await nd.pages.list();
await nd.pages.create({ slug: "about", title: "About us", meta_title: "About Linden Bakery" });
await nd.pages.update(12, { meta_description: "Sourdough since 1998." });
await nd.components.register({
html: '<section><h1 data-translate="hero.title">Fresh bread</h1></section>',
intent: "hero",
page_slug: "home",
});Pages of the hosted site, in its own design
Add pages and sub-pages to a hosted site without writing HTML. The new page reuses one of the site's pages (its layout, header, footer and styles); its texts and pictures are its own.
const page = await nd.sitePages.create({
title: "Wedding cakes",
path: "services/wedding-cakes", // sub-pages work
template: "/", // start from the home page (default)
content: "copy", // or "empty": a heading and a paragraph in the site's styles
menu: true, // link it in the site's menu
});
// page.url → https://linden-bakery.neuraldraft.io/services/wedding-cakes (and /hr/… for every language)
await nd.siteTexts.update({ texts: { "page.services-wedding-cakes.home.title": "Wedding cakes in Bath" } });
await nd.sitePages.update("services/wedding-cakes", { published: false }); // take it offline
await nd.sitePages.delete("services/wedding-cakes");
const { data: all } = await nd.sitePages.list();Scopes: pages:read, pages:write. Pages of the site as it was built can't be deleted.
pages are the API's page records with SEO meta. Hosted pages published with the connector's publish_site_page and sites deployed with deploy_site are managed through the connector; their editable blocks are in siteTexts.
Blog
// A post you wrote: 0 credits
const post = await nd.blogPosts.create({ title: "Our new rye", content: "<p>…</p>", language_code: "en" });
await nd.blogPosts.publish(post.id);
// Researched and written by our pipeline (with images): 60 credits, async
const job = await nd.blogPosts.generateAi({ topic: "How to store sourdough", word_count: 1200 });
const done = await nd.jobs.poll(job.id);
await nd.blogPosts.translate(post.id, ["de", "hr"]); // 10 credits per language
const list = await nd.blogPosts.list({ status: "published", lang: "en" });Bookings
const services = await nd.booking.listServices({ status: "active" });
const svc = await nd.booking.createService({ name: "Check-up and clean", duration_minutes: 45, price: 6500, currency: "GBP" });
const free = await nd.booking.availability({ service_id: svc.id, from: "2026-11-02", to: "2026-11-06" });
// { "2026-11-02": [{ start, end, capacity_remaining }], … }
const pending = await nd.bookings.list({ status: "pending", from: "2026-11-01" });
await nd.bookings.confirm(pending.data[0].id); // the customer gets an email
await nd.bookings.cancel(41, { reason: "Dentist ill" });
await nd.bookings.complete(40);
await nd.bookings.noShow(39);
const week = await nd.bookings.calendar({ from: "2026-11-02", to: "2026-11-08" });
const stats = await nd.bookings.stats(); // today, upcoming, pending, month revenue (cents)
await nd.booking.updateSettings({ auto_confirm_bookings: true, notification_email: "[email protected]" });Also: bookings.get/create/update/delete, booking.getService/updateService/deleteService/getSettings.
Forms and newsletter
const replies = await nd.forms.listSubmissions({ search: "parking" });
const subs = await nd.forms.listSubscribers({ subscribed: true, language: "en" });
await nd.forms.deleteSubmission(replies.data[0].id);
await nd.forms.deleteSubscriber(17);Shop
const products = await nd.products.list({ status: "active" });
await nd.products.update(42, { price: 2799 }); // minor units (cents)
const variants = await nd.products.listVariants(42);
await nd.products.createVariant(42, { name: "Large", price: 3299 });
const cats = await nd.productCategories.list();
await nd.productCategories.create({ name: "Bread" });
const orders = await nd.orders.list({ status: "paid" });
await nd.orders.update(orders.data[0].id, { status: "shipped", tracking_number: "RM123456GB" });
await nd.orders.refund(orders.data[0].id); // card orders only; full refund unless { amount }Card payments go to the business's own Stripe account: connect it in the dashboard under Shop → Payouts. Without Stripe the shop takes pay-on-pickup, bank transfer or order requests.
Social posts
Schedule and publish posts with media you supply (your photos, videos, or ads made in Batchcraft). We don't offer AI image or video creation. We craft finished static ads and reels for your brand.
const accounts = await nd.social.listAccounts(); // health: ok | expiring | expired | inactive
const post = await nd.social.createPost({
title: "Rye is back",
platforms: ["facebook", "instagram"],
facebook_content: "Our rye is back on Saturday.",
instagram_content: "Rye is back 🍞 Saturday from 7:00.",
media_url: "https://cdn.example.com/rye.jpg",
external_ref: "rye-2026-11", // idempotency key
});
await nd.social.schedulePost(post.id, "2026-11-07T08:00:00Z");
// nd.social.publishPost(post.id) publishes now (public; returns a job)Also: listPosts, getPost, updatePost, deletePost.
Webhooks
const ep = await nd.webhooks.createEndpoint({
url: "https://example.com/hooks/neuraldraft", // https only
events: ["booking.confirmed", "order.paid", "content.changed"],
});
console.log(ep.signing_secret); // shown once; store it
const log = await nd.webhooks.listDeliveries({ endpoint_id: ep.id });
await nd.webhooks.retryDelivery(log.data[0].id);Also: listEndpoints, updateEndpoint, deleteEndpoint, rotateSecret, getDelivery.
Project and nd.js on other websites
await nd.projects.update({ allowed_origins: ["https://www.client.co.uk"] });
const usage = await nd.projects.usage(); // credits balance and this period's spendallowed_origins lets nd.js blocks loaded from the workspace address (https://<name>.neuraldraft.io/_nd/nd.js) run on a site hosted elsewhere. The workspace address, its custom domains and the saved website are always allowed. https only (http://localhost:<port> for testing), at most 20; the list is replaced, [] clears it.
Images
await nd.images.upload("hero", fileOrBlob, { filename: "hero.jpg" }); // your own picture
await nd.images.replace("hero", { url: "https://cdn.example.com/hero.webp" });
const all = await nd.images.list({ prefix: "gallery." });images.generate() and replace(key, { regenerate: true }) are deprecated: AI image and video creation is switched off and the API answers 410 Gone with code media_generation_moved. Uploads and URL swaps work as before.
Credits
1 credit = $0.01. Only work our own AI pipeline does costs credits: a blog post 60, a translation 10 per language. Everything else through the API is free: content, page, image and component writes, bookings, forms, shop, social scheduling. Rate limits apply instead. Plans and credits: neuraldraft.io/pricing.
Errors
Every method throws ApiError on a non-2xx response (status, body with the API's JSON, path). status: 0 is a network failure or timeout.
import { ApiError } from "@neuraldraft/sdk";
try {
await nd.blogPosts.generateAi({ topic: "…" });
} catch (err) {
if (err instanceof ApiError) {
const { code } = JSON.parse(err.body || "{}") as { code?: string };
if (err.status === 402) { /* out of credits */ }
else if (err.status === 410 && code === "media_generation_moved") { /* AI image/video creation is off */ }
else if (err.status === 429) { /* rate limited: back off */ }
else throw err;
}
}Pagination and jobs
Lists return { data, meta: { page, page_size, total, last_page } } (webhook deliveries use Laravel's { data, links, meta: { current_page, … } }). Slow work (blog writing, translations, publishing a social post now) returns a job: await nd.jobs.poll(job.id) waits until it is completed, failed or cancelled.
Upgrading from 0.3
Nothing was removed. New: NeuralDraftWorkspaces, NeuralDraftClient.fromEnv, siteTexts, content.list, bookings, booking.createService/updateService/deleteService/availability/getSettings/updateSettings, forms, orders, productCategories, product variants, social, webhooks, allowed_origins on projects.update. images.generate is deprecated (410).
Development
npm install
npm run lint && npm test && npm run buildLinks
- Documentation
- Developer MCP
@neuraldraft/mcpfor Claude Code, Cursor and Codex - For developers
- Issues
