crontent
Integration·Directus·beginner

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_KEY and CRONTENT_WEBHOOK_SECRET
  • Directus instance with a posts collection (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:

ts
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 fieldDirectus fieldNotes
titletitleString, display H1
seo_titleseo_titleMeta title; fall back to title
slugslugString, unique
excerptexcerptText
body_mdx / body_htmlbodyWYSIWYG or text field
tagstagsJSON array or M2M relation
hero_image_urlhero_imageFile URL or string
canonical_urlcanonical_urlString
sources[]sourcesJSON field
ready_atcrontent_ready_atTimestamp

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.

ts
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.

ts
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.

Official documentation