Skip to content
← Docs Docs · Content API

A headless CMS for content you didn't have to build

Read-only, publishable-key auth, edge-cached on Cloudflare. Fetch finished, quality-gated articles into any frontend — the same API this marketing blog runs on.

Base URL & authentication

Base: https://cdn.draftwave.io — a dedicated worker, kept separate from the dashboard API so it can be cached hard and scaled independently. Every /v1/* route requires a key, sent as a bearer token or a query param:

Authorization: Bearer pk_live_xxx
# or, for simple frontends:
GET https://cdn.draftwave.io/v1/articles?site=SITE_ID&key=pk_live_xxx
pk_live_… Publishable (read) key

Read-only, scoped to published content for one site. Safe to ship in a browser — can be origin-restricted (a present, non-allowlisted Origin header is rejected with 403) and is what powers content fetching.

sk_live_… Secret (admin) key

Server-side only — broader access for scripting generation, drafts, and topics. Never expose it in a browser. Draftwave stores only a SHA-256 hash of every key and shows the full key exactly once, at creation.

Endpoints

Every endpoint only ever returns status = published content scoped to the key's site — drafts and pending articles are never exposed, no matter which key you use.

Method Endpoint Query Description
GET /v1/articles ?site=&limit=&cursor=&since=&tag= List published articles for a site. Paginated (opaque cursor, limit capped at 100); since=<ms> powers incremental sync.
GET /v1/articles/{slug} ?site= Fetch one published article by slug, with hreflang alternates for other published language variants.
GET /v1/articles/{id}/raw ?site= The article body as Markdown — { markdown } — for consumers that don't want HTML.
GET /v1/sites/{siteId}/manifest ?since= ids + updatedAt only — the cheapest way to know what changed for an incremental static build.
GET /v1/sites/{siteId}/sitemap.xml XML sitemap of published articles for the site's own domain.
GET /v1/sites/{siteId}/feed.xml RSS 2.0 feed of the 50 most recent published articles.
GET /v1/sites/{siteId}/llms.txt A curated, AI-crawler-friendly index of published content, grouped by topic cluster — 'robots.txt for the AI era.'

The PublicArticle shape

Every article endpoint returns (or lists) the same object — rendered HTML, an optional Markdown body, FAQ pairs, and inject-ready JSON-LD, so you don't have to reassemble anything client-side.

interface PublicArticle {
  id: string; slug: string;
  title: string; metaTitle: string; metaDescription: string;
  html: string;                 // rendered from Markdown server-side
  markdown?: string;            // only with ?include=markdown
  excerpt: string;
  faq: { q: string; a: string }[];
  jsonLd: unknown;              // ready to inject into <head>
  hero: { url: string; alt: string } | null;
  images: { url: string; alt: string }[];
  cluster?: { id: string; title: string };
  internalLinks: { slug: string; anchor: string }[];
  tags: string[];
  language: string;
  alternates?: { language: string; slug: string }[];
  publishedAt: number; updatedAt: number;
}

Examples

curl — the 10 most recent published articles

curl "https://cdn.draftwave.io/v1/articles?site=SITE_ID&limit=10" \
  -H "Authorization: Bearer pk_live_xxx"

JS / Vue frontend (browser, origin-locked key)

const res = await fetch(
  "https://cdn.draftwave.io/v1/articles?site=SITE_ID&limit=10",
  { headers: { Authorization: "Bearer pk_live_xxx" } }
);
const { data } = await res.json();

Incremental static build (server-side, in a build step)

const { items } = await fetch(
  `https://cdn.draftwave.io/v1/sites/SITE_ID/manifest?since=${lastBuildMs}`,
  { headers: { Authorization: `Bearer ${process.env.DRAFTWAVE_KEY}` } }
).then((r) => r.json());
// fetch only the changed slugs, regenerate just those pages

Caching & invalidation

Every response carries an ETag and Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=86400. Send If-None-Match and get a 304 back for free. Publishing, editing, or unpublishing an article bumps a per-site version counter that's folded into every ETag, so caches invalidate correctly the moment content changes — no stale reads.

Rate limits

Each key is limited to 120 requests per 60-second window by default. Go over it and you'll get a 429 with a Retry-After header telling you exactly how long to back off.

Webhooks

For JAMstack and static-site consumers that want to rebuild the moment content changes, register a URL (typically your Vercel/Cloudflare deploy hook) and Draftwave POSTs on every event with an HMAC signature in X-Draftwave-Signature for you to verify:

site.article.published    { siteId, articleId, slug, url }
site.article.updated
site.article.unpublished

Content API FAQ

Do I need a secret key just to read published content?

No — a publishable key is all you need for every endpoint on this page. Secret keys are only for scripting write actions (triggering generation, reading drafts, managing topics) against the dashboard API, not the Content API.

What happens if I lose or leak a key?

Revoke it from Settings → API Keys and rotate — a revoked key returns 401 immediately. Because only a hash is stored, Draftwave can never show you the raw key again after creation, so treat the one-time reveal like a password.

Pull your first article via the API.

Start free, generate an article, and fetch it with a publishable key.