Documentation Index
Fetch the complete documentation index at: https://docs.neuraldraft.io/llms.txt
Use this file to discover all available pages before exploring further.
This snippet wires Neural Draft into Astro 5 via a content collection loader.
The result: type-safe blog content fetched at build time, plus a webhook
handler that triggers Vercel/Cloudflare/Netlify rebuilds when something
changes.
Install
npm install @neuraldraft/sdk
NEURAL_DRAFT_API_KEY=ndsk_live_...
NEURAL_DRAFT_WEBHOOK_SECRET=whsec_...
Shared client
import { NeuralDraft } from "@neuraldraft/sdk";
import { createHmac, timingSafeEqual } from "node:crypto";
const apiKey = import.meta.env.NEURAL_DRAFT_API_KEY ?? process.env.NEURAL_DRAFT_API_KEY;
if (!apiKey) throw new Error("Missing NEURAL_DRAFT_API_KEY");
export const nd = new NeuralDraft({ apiKey });
export function verifyNeuralDraftSignature(
body: string,
header: string,
secret = import.meta.env.NEURAL_DRAFT_WEBHOOK_SECRET ?? "",
toleranceSeconds = 300
): boolean {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string])
);
const t = Number(parts.t);
const v1 = parts.v1;
if (!Number.isFinite(t) || !v1 || !secret) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - t) > toleranceSeconds) return false;
const signed = `${t}.${body}`;
const expected = createHmac("sha256", secret).update(signed).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(v1, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
A content collection loader
Astro’s content layer treats Neural Draft like a CMS — pull posts at build time,
get type-safe content with Zod validation.
import { defineCollection, z } from "astro:content";
import { nd } from "./lib/neural-draft";
const blog = defineCollection({
loader: async () => {
const all: any[] = [];
let page = 1;
while (true) {
const res = await nd.blogPosts.list({
status: "published",
lang: "en",
per_page: 100,
page,
});
all.push(...res.data);
if (res.data.length < 100) break;
page++;
}
return all.map((p) => ({
id: p.slug,
slug: p.slug,
title: p.title,
excerpt: p.excerpt,
content: p.content,
featured_image: p.featured_image,
published_at: p.published_at,
category: p.category?.name ?? null,
tags: (p.tags ?? []).map((t: any) => t.name),
}));
},
schema: z.object({
slug: z.string(),
title: z.string(),
excerpt: z.string().nullable(),
content: z.string(),
featured_image: z.string().url().nullable(),
published_at: z.string().nullable(),
category: z.string().nullable(),
tags: z.array(z.string()),
}),
});
export const collections = { blog };
A blog index page
---
import { getCollection } from "astro:content";
const posts = (await getCollection("blog")).sort((a, b) =>
(b.data.published_at ?? "").localeCompare(a.data.published_at ?? "")
);
---
<main class="prose mx-auto py-12">
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.data.slug}`}>
<h2>{post.data.title}</h2>
<p>{post.data.excerpt}</p>
</a>
</li>
))}
</ul>
</main>
---
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({ params: { slug: post.data.slug }, props: { post } }));
}
const { post } = Astro.props;
---
<article class="prose mx-auto py-12">
<h1>{post.data.title}</h1>
{post.data.featured_image && (
<img src={post.data.featured_image} alt={post.data.title} />
)}
<div set:html={post.data.content} />
</article>
A webhook handler that triggers a rebuild
Astro itself doesn’t trigger rebuilds, but every modern host (Vercel,
Netlify, Cloudflare) gives you a “deploy hook” URL. Forward Neural Draft
webhooks to that URL.
import type { APIRoute } from "astro";
import { verifyNeuralDraftSignature } from "../../lib/neural-draft";
const REBUILD_HOOK = process.env.DEPLOY_HOOK_URL!;
export const POST: APIRoute = async ({ request }) => {
const body = await request.text();
const sig = request.headers.get("x-neural-draft-signature") ?? "";
if (!verifyNeuralDraftSignature(body, sig)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(body) as { type: string };
if (
event.type === "blog_post.published" ||
event.type === "blog_post.translated" ||
event.type === "content.changed"
) {
await fetch(REBUILD_HOOK, { method: "POST" });
}
return new Response(JSON.stringify({ received: true }), {
headers: { "content-type": "application/json" },
});
};
Notes
- This file uses Astro’s
content.config.ts — Astro 5 syntax. On 4.x, use the
defineCollection({ loader }) style from the experimental content layer.
- For multi-language sites, add
import.meta.env.PUBLIC_LANG and clone the
collection per locale.
- The deploy hook is per-platform: Vercel, Netlify and Cloudflare Pages all
expose one in project settings. Free, no rate limit.