Skip to main content
The CMS pillar is what turns a static AI-generated site into a real, editable property. Every text node and image you mark up with our conventions becomes a field in the project’s admin — no schemas, no migrations, no glue code.

What this gives you

  • Translation keys — dot-namespaced keys (hero.headline, pricing.tiers.0.title) with per-language values. One bulk endpoint feeds your build process.
  • Image keysdata-image-key="hero.background" on an <img>. The customer can swap the image in the admin; your build resolves the URL.
  • Brand context — voice, colors, fonts, audience exposed as a single resource. Read once at build time, or hot-fetch per request.
  • Register-component — turn AI-generated HTML into an admin-editable section in one call, with data-translate keys auto-discovered.

Quick example

Read a single key, then upsert one. The SDK exposes both as one-liners.
import { NeuralDraftClient } from "@neuraldraft/sdk";

const nd = new NeuralDraftClient({ apiKey: process.env.NEURALDRAFT_API_KEY! });

// Read one key (response includes `all_locales` with every translation)
const hero = await nd.content.get("hero.headline", { lang: "en" });
console.log(hero.value);

// Upsert (creates if missing). Charges 1 credit (`content_update`).
await nd.content.set("hero.headline", "Welcome to Acme", "en");
Bulk read response shape:
{
  "data": {
    "hero.headline": "Welcome",
    "hero.subhead": "We build software that scales.",
    "cta.label": "Get started"
  },
  "meta": { "lang": "en", "page": 1, "page_size": 200, "total": 3 }
}
Seed many keys at once with bulkCreate (idempotent — existing keys are skipped, not failed):
await nd.content.bulkCreate(
  {
    "hero.headline": "Welcome",
    "hero.subhead": "We build software that scales.",
    "cta.label": "Get started",
  },
  "en",
);
// => { created: ["hero.headline", "hero.subhead", "cta.label"], skipped_existing: [] }
CMS writes cost 1 credit each. PUT /v1/content/{key} (content_update), POST/PATCH /v1/pages (page_update), and registering an image via URL swap on PUT /v1/images/{key} or multipart upload on POST /v1/images (image_register) each consume 1 credit on success. AI generation (image, translation, blog, website) is metered separately at its own rate. Reads (GET) remain free. See pricing.

Common workflows

1. Build-time fetch for SSG

Static-site generators (Next.js, Astro, Nuxt, Hugo) call /v1/content/bulk once per locale at build time. Cache the response — re-fetch only when the content.changed webhook fires.

2. Click-to-edit overlay on a live site

Drop a tiny JS snippet into your HTML. It looks for data-translate attributes and turns them into inline-editable fields when the user is signed into the admin in the same browser.
<script src="https://widgets.neuraldraft.io/v1/edit-overlay.js" defer></script>
The overlay calls /v1/content/{key} on save. Same backend, no rebuild.

3. AI registers a generated section

When the AI in your editor generates a hero, it calls register_component(html, intent) via MCP. The component appears in your admin instantly:
POST /v1/components/register
{
  "html": "<section data-component='hero'><h1 data-translate='hero.headline'>Welcome</h1></section>",
  "intent": "marketing hero"
}
{
  "component_id": "cmp_2NgcaXxFqLPo",
  "editor_url": "https://app.neuraldraft.io/components/cmp_2NgcaXxFqLPo",
  "keys_created": ["hero.headline"]
}
The admin auto-discovers hero.headline as an editable text field. No schema work.

4. Translate a key into N languages

SDK
const job = await nd.content.translate("hero.headline", ["fr", "de", "es"]);
const finished = await nd.jobs.poll(job.id);
// finished.status === "completed"
cURL
curl -X POST https://api.neuraldraft.io/v1/content/hero.headline/translate \
  -H "Authorization: Bearer $NEURALDRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_langs":["fr","de","es"]}'
Returns a Job reference. Poll /v1/jobs/{id} (or nd.jobs.poll(id)) until terminal, or subscribe to the content.changed webhook to get notified once each language lands. Charges 7 credits per target language (translate_language).

5. Brand context for AI prompts

Read brand once, pass it into your AI calls. The MCP server does this for you; if you’re calling the API directly:
SDK
const brand = await nd.brand.get();
// Patch a field — `null` clears it.
await nd.brand.update({ voice: "friendly_professional" });
cURL
curl https://api.neuraldraft.io/v1/brand \
  -H "Authorization: Bearer $NEURALDRAFT_API_KEY"
{
  "voice": "friendly_professional",
  "audience": "indie founders building with AI tools",
  "content_tone": "warm",
  "colors": {
    "primary": { "hex": "#7C3AED", "name": "Purple" },
    "secondary": { "hex": "#0F172A", "name": "Ink" },
    "accent": { "hex": "#A78BFA", "name": "Lavender" }
  },
  "fonts": { "heading": "Inter", "body": "Inter" },
  "default_language": "en",
  "target_languages": ["en", "fr", "de"],
  "requires_branding_badge": false
}

Conventions

The two attributes the AI uses everywhere:
<h1 data-translate="hero.headline">Welcome</h1>
<p data-translate="hero.subhead">We build software that scales.</p>

<img data-image-key="hero.background" src="..." alt="...">
Keys are case-sensitive, dot-namespaced, and lowercase by convention. Index keys for arrays follow section.field.0.subfield.

Per-page SEO meta

Search engines and social cards need per-route metadata — <title>, <meta name=description>, and the og:* tags. Neural Draft stores this on the page itself so each route can have its own values without polluting the translation-key namespace. There are two surfaces depending on what kind of content you’re rendering:

CMS-managed pages — GET /v1/pages/{slug}

For everything that isn’t a blog post (homepage, about, services, legal, landing pages…), fetch the Page resource at build time and use the meta fields directly. Every field is nullable; an empty meta_title means the page title is used as the document title.
curl https://api.neuraldraft.io/v1/pages/about \
  -H "Authorization: Bearer $NEURALDRAFT_API_KEY"
Use nd.pages.update(id, { meta_title: null }) (or pass null via cURL) to clear a single meta field. Untouched fields are preserved — the API merges the patch into the existing record.

Blog posts — GET /v1/blog-posts/{slug}

Posts have their own per-translation meta. The locale-aware response picks the matching translation for meta_title / meta_description. Use featured_image for og:image (it’s the canonical image associated with the post).
curl "https://api.neuraldraft.io/v1/blog-posts/5-minute-breathwork-for-anxious-mornings?lang=en" \
  -H "Authorization: Bearer $NEURALDRAFT_API_KEY"
To patch SEO meta on a post (e.g. tighten the description after publish):
await nd.blogPosts.update(post.id, {
  meta_title: "5-minute breathwork — Acme blog",
  meta_description: "A 5-minute morning routine to take the edge off anxiety.",
});

Where to put what in your <head>

<title>{page.meta_title ?? page.title}</title>
<meta name="description" content={page.meta_description ?? ""} />
<meta property="og:title" content={page.og_title ?? page.meta_title ?? page.title} />
<meta property="og:description" content={page.og_description ?? page.meta_description ?? ""} />
<meta property="og:image" content={page.og_image ?? defaultOgImage} />
{page.canonical_url && <link rel="canonical" href={page.canonical_url} />}
{page.exclude_from_search && <meta name="robots" content="noindex" />}
For a blog post the same template applies, swapping page.og_image with post.featured_image.

Images

Three flavours, all routing through the same Image resource:
// 1. AI-generate (32 credits, async — returns a Job)
const job = await nd.images.generate({
  prompt: "warm morning light over a yoga mat, soft focus",
  aspect_ratio: "16:9",
  key: "hero.background",
});
const finished = await nd.jobs.poll(job.id);
// finished.result => { url, key, width, height }

// 2. Direct upload from a File / Blob / Buffer (1 credit, sync)
await nd.images.upload("hero.background", file, { filename: "hero.jpg" });

// 3. Replace by URL — no AI roundtrip (1 credit, sync)
await nd.images.replace("hero.background", { url: "https://cdn.example.com/x.jpg" });

// Resolve / list / delete
const img = await nd.images.get("hero.background");
const { data } = await nd.images.list({ prefix: "hero.", page_size: 50 });
await nd.images.delete("hero.background");
Both upload() and replaceFile() accept a Web File/Blob (browser, Bun, Node 20+) or a Node Buffer/Uint8Array. For raw buffers, supply opts.filename so the server can derive the file extension.

Reference

EndpointTag
GET /v1/content/bulkContent
GET /v1/content/{key}Content
PUT /v1/content/{key}Content
PUT /v1/content (batch)Content
POST /v1/content/{key}/translateContent
POST /v1/components/registerComponents
GET /v1/componentsComponents
GET /v1/pagesPages
GET /v1/pages/{idOrSlug}Pages
POST /v1/pagesPages
PATCH /v1/pages/{id}Pages
DELETE /v1/pages/{id}Pages
GET /v1/brandBrand
PATCH /v1/brandBrand
GET /v1/imagesImages
GET /v1/images/{key}Images
POST /v1/images (AI generate or multipart upload)Images
PUT /v1/images/{key} (multipart, URL swap, or regenerate)Images
DELETE /v1/images/{key}Images
POST /v1/newsletters/subscribe (public)Forms
GET /v1/newslettersForms
DELETE /v1/newsletters/{id}Forms
POST /v1/contact-forms (public)Forms
GET /v1/contact-formsForms
DELETE /v1/contact-forms/{id}Forms

Forms — newsletter & contact

Most AI-built sites need two simple capture surfaces: an email-collector for newsletters and a free-form contact form. Neural Draft ships both as first-class endpoints — no schemas to design, no spam-handling glue, and free (no credits charged, since these are storage + delivery, not AI).

Public submit

The submit endpoints are public. Authenticate the project with a server-side API key in X-NeuralDraft-Project-Key (or pass ?project_id= for unauthenticated client-side embeds where you don’t want to ship a key):
# Newsletter — idempotent. Re-submitting an existing email returns 200 with
# status="already_subscribed" instead of an error.
curl -X POST https://api.neuraldraft.io/v1/newsletters/subscribe \
  -H "X-NeuralDraft-Project-Key: $NEURALDRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","app_lead":false}'

# Contact form — `data` is free-form JSON for whatever your form collects;
# IP and user-agent are merged in server-side for spam triage.
curl -X POST https://api.neuraldraft.io/v1/contact-forms \
  -H "X-NeuralDraft-Project-Key: $NEURALDRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "subject": "Demo request",
    "message": "We are evaluating Neural Draft for our next launch.",
    "data": {"company":"Acme","headcount":12}
  }'

Reading submissions

Form submissions are not in the outgoing-webhook event whitelist today — poll the admin list endpoints (GET /v1/newsletters, GET /v1/contact-forms) or use the MCP tools list_newsletter_subscribers / list_contact_form_submissions to inspect what came in. We may add newsletter.subscribed / contact_form.submitted topics to the webhook whitelist in a future release; subscribe to the changelog if you’d like to be notified.

Admin list & delete

Listing and deleting submissions requires an API key with forms:read / forms:write scope (or *). Both list endpoints support ?page / ?page_size (max 200) and ?search=; the newsletter list also accepts ?app_lead=true to filter out trial leads from your real subscriber list.