AI content for Directus
AI content for Directus: create draft Directus items from Crontent research posts via verified webhooks and the REST API.
How-to: Integrate Crontent with Directus. Official docs: Directus. Shared API contract: Publishing API and SDK.
Verify the Crontent webhook, fetch the post, and create a Directus item with status: draft via the REST API. Do not publish from the receiver.
What you need
- Crontent
CRONTENT_API_KEYandCRONTENT_WEBHOOK_SECRET - Directus instance with a
postscollection (title, slug, body, etc.) - Static access token or admin user token with create permission on
posts - Webhook receiver (serverless or self-hosted)
Register the webhook:
await crontent.webhooks.register({
projectId: process.env.CRONTENT_PROJECT_ID!,
url: "https://your-app.com/api/crontent/webhook",
events: ["post.ready"],
});Map Crontent fields to Directus
| Crontent field | Directus field | Notes |
|---|---|---|
title | title | String, display H1 |
seo_title | seo_title | Meta title; fall back to title |
slug | slug | String, unique |
excerpt | excerpt | Text |
body_mdx / body_html | body | WYSIWYG or text field |
tags | tags | JSON array or M2M relation |
hero_image_url | hero_image | File URL or string |
canonical_url | canonical_url | String |
sources[] | sources | JSON field |
ready_at | crontent_ready_at | Timestamp |
Crontent produces researched drafts from your brief; you control angle. Directus drafts (status: "draft") stay hidden from public API until published.
Receive and verify webhooks
Use X-Crontent-Delivery as idempotency key. Retries: 1m, 5m, 30m, 2h, 12h.
import { Crontent } from "@crontent/sdk";
const crontent = new Crontent({ apiKey: process.env.CRONTENT_API_KEY! });
export async function POST(req: Request) {
const rawBody = await req.text();
const deliveryId = req.headers.get("x-crontent-delivery")!;
if (await seen(deliveryId)) return new Response("OK");
const valid = await crontent.webhooks.verify({
rawBody,
signature: req.headers.get("x-crontent-signature"),
timestamp: req.headers.get("x-crontent-timestamp"),
secret: process.env.CRONTENT_WEBHOOK_SECRET!,
});
if (!valid) return new Response("Unauthorized", { status: 401 });
const { post_id } = JSON.parse(rawBody);
const post = await crontent.posts.get(post_id);
await createDirectusDraft(post);
await markSeen(deliveryId);
return new Response("OK");
}Create a draft in Directus
POST to /items/posts with status: "draft". Directus Content Versioning can also track draft vs published if enabled.
async function createDirectusDraft(post: Post) {
const res = await fetch(`${process.env.DIRECTUS_URL}/items/posts`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DIRECTUS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: post.title,
seo_title: post.seo_title ?? post.title,
slug: post.slug,
excerpt: post.excerpt,
body: post.body_html ?? post.body_mdx,
tags: post.tags,
sources: post.sources,
crontent_post_id: post.id,
status: "draft",
}),
});
if (!res.ok) throw new Error(await res.text());
}Backfill and polling
Use crontent.posts.list({ projectId, since, cursor }) and filter by existing crontent_post_id in Directus. Run periodically or after fixing webhook config.