Search documentation

Find pages, sections, and snippets across the Clog docs.

Framework-agnostic fetch / curl

Short copy-pasteable recipes for the most common tasks — no framework required.

Snippets you can drop into any language with an HTTP client. The JS examples use fetch (works in Node 20+, every browser, Deno, Bun, and edge runtimes). The base URL is https://api.clog.dev for every consumer — replace clog_live_xxx with the key you issued in the dashboard.

Get a single post by slug

curl
curl https://api.clog.dev/api/v1/external/posts/my-first-post \
  -H "X-API-Key: clog_live_xxx"
fetch
const res = await fetch(
  'https://api.clog.dev/api/v1/external/posts/my-first-post',
  { headers: { 'X-API-Key': process.env.CLOG_API_KEY! } },
);
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Clog ${res.status}`);
const post = await res.json();

Get a post by UUID

The same idOrSlug endpoint accepts either:

curl
curl https://api.clog.dev/api/v1/external/posts/0a4b3c2d-1234-4567-89ab-cdef01234567 \
  -H "X-API-Key: clog_live_xxx"

List published posts, newest first

curl
curl 'https://api.clog.dev/api/v1/external/posts?status=published&page=1&pageSize=10&order=desc&orderBy=publishedAt' \
  -H "X-API-Key: clog_live_xxx"
fetch
const params = new URLSearchParams({
  status: 'published',
  page: '1',
  pageSize: '10',
  order: 'desc',
  orderBy: 'publishedAt',
});

const res = await fetch(
  `https://api.clog.dev/api/v1/external/posts?${params}`,
  { headers: { 'X-API-Key': process.env.CLOG_API_KEY! } },
);
const result = await res.json();
for (const post of result.data) {
  console.log(post.title, post.slug);
}

Walk every page of a list

PaginatedResponse carries totalPages — loop until you've seen them all.

fetch
async function fetchAllPosts() {
  const all: unknown[] = [];
  let page = 1;
  while (true) {
    const res = await fetch(
      `https://api.clog.dev/api/v1/external/posts?status=published&page=${page}&pageSize=100`,
      { headers: { 'X-API-Key': process.env.CLOG_API_KEY! } },
    );
    const json = await res.json();
    all.push(...json.data);
    if (page >= json.totalPages) break;
    page += 1;
  }
  return all;
}

Filter posts by author, category, or tag

curl — by author
curl 'https://api.clog.dev/api/v1/external/posts?authorId=0a4b3c2d-...&status=published' \
  -H "X-API-Key: clog_live_xxx"
curl — by category
curl 'https://api.clog.dev/api/v1/external/posts?categoryId=8e3f7c19-...&status=published' \
  -H "X-API-Key: clog_live_xxx"
curl — uncategorised only
curl 'https://api.clog.dev/api/v1/external/posts?categoryId=&status=published' \
  -H "X-API-Key: clog_live_xxx"
curl — by tag
curl 'https://api.clog.dev/api/v1/external/posts?tagId=4d2e8a3c-...&status=published' \
  -H "X-API-Key: clog_live_xxx"

q searches title, excerpt, and bodyText case-insensitively.

curl
curl 'https://api.clog.dev/api/v1/external/posts?q=migration&status=published' \
  -H "X-API-Key: clog_live_xxx"

Create a post

curl
curl -X POST https://api.clog.dev/api/v1/external/posts \
  -H "X-API-Key: clog_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "hello-world",
    "title": "Hello, world",
    "authorId": "0a4b3c2d-1234-4567-89ab-cdef01234567",
    "bodyJson": [
      { "type": "paragraph", "text": "My first post via the API." }
    ],
    "status": "published"
  }'
fetch
const created = await fetch('https://api.clog.dev/api/v1/external/posts', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.CLOG_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    slug: 'hello-world',
    title: 'Hello, world',
    authorId: '0a4b3c2d-...',
    bodyJson: [{ type: 'paragraph', text: 'My first post via the API.' }],
    status: 'published',
  }),
}).then(r => r.json());

Requires posts:write. Returns 201 with the full PostResponse. 409 on slug collision; 400 on a cross-workspace FK (e.g. an authorId from another workspace).

Update a post

curl
curl -X PATCH https://api.clog.dev/api/v1/external/posts/hello-world \
  -H "X-API-Key: clog_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Hello, World!",
    "seo": { "title": "Hello, World! — Acme Blog", "focusKeyword": "hello world" }
  }'

PATCH is a partial update — every field is optional. Changing bodyJson re-derives bodyText, tocItems, and readingTimeMin.

Soft-delete a post

curl
curl -X DELETE https://api.clog.dev/api/v1/external/posts/hello-world \
  -H "X-API-Key: clog_live_xxx"

Returns the pre-delete snapshot with deletedAt set. The slug becomes available for reuse; the post stops appearing in all reads.

Attach / detach tags

curl — attach
curl -X POST https://api.clog.dev/api/v1/external/posts/hello-world/tags \
  -H "X-API-Key: clog_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "tagIds": ["4d2e8a3c-...", "9c1f7b40-..."] }'
curl — detach
curl -X DELETE https://api.clog.dev/api/v1/external/posts/hello-world/tags \
  -H "X-API-Key: clog_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "tagIds": ["4d2e8a3c-..."] }'

Both are idempotent — re-attaching an attached tag (or detaching one that isn't attached) is a no-op.

Get a page

Pages mirror the post shape, minus author/category/featuredMedia/tags.

curl
curl https://api.clog.dev/api/v1/external/pages/about \
  -H "X-API-Key: clog_live_xxx"

Get categories, tags, authors

All paginate the same way:

curl
curl 'https://api.clog.dev/api/v1/external/categories?page=1&pageSize=50' -H "X-API-Key: clog_live_xxx"
curl 'https://api.clog.dev/api/v1/external/tags?page=1&pageSize=100' -H "X-API-Key: clog_live_xxx"
curl 'https://api.clog.dev/api/v1/external/authors?isActive=true' -H "X-API-Key: clog_live_xxx"

Single-resource reads accept id-or-slug:

curl
curl https://api.clog.dev/api/v1/external/categories/news -H "X-API-Key: clog_live_xxx"
curl https://api.clog.dev/api/v1/external/authors/jane-doe -H "X-API-Key: clog_live_xxx"

Get workspace branding + publisher

curl
curl https://api.clog.dev/api/v1/external/workspace \
  -H "X-API-Key: clog_live_xxx"

Returns the brand profile (name, logoUrl, primaryColor, aboutMd, ...) plus the publisher Organization (used in jsonLd across the site), site verification codes, and the resolved defaultOgImageUrl. Useful at build time for site-wide <head> tags.

Sync redirects

Pull all redirects, sorted for stable diffing:

curl
curl 'https://api.clog.dev/api/v1/external/redirects?pageSize=500&order=asc&orderBy=fromPath' \
  -H "X-API-Key: clog_live_xxx"

Replay each match in your edge / middleware with the matching HTTP status (Permanent301 → 301, Gone410 → 410, etc.). See Redirects and link health for the status table.

Report a 404

Fire-and-forget on every 404 your site serves:

curl
curl -X POST https://api.clog.dev/api/v1/external/link-health \
  -H "X-API-Key: clog_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "path": "/posts/missing-thing", "referrer": "https://example.com/sitemap" }'

The endpoint upserts on (workspaceId, path) and increments hitCount — repeat reports are explicitly safe.

Get sitemap data

curl
curl https://api.clog.dev/api/v1/external/feeds/sitemap-data \
  -H "X-API-Key: clog_live_xxx"
{
  "data": [
    { "url": "/posts/my-first-post", "lastmod": "2026-05-23T12:34:56.789Z", "changefreq": "weekly", "priority": 0.7 },
    { "url": "/pages/about",         "lastmod": "2026-05-12T09:00:00.000Z", "changefreq": "monthly", "priority": 0.5 }
  ]
}

Prepend your site's origin to each url and stamp the XML yourself.

Generic error handling

Every error returns the same envelope. Branch on code, not on message.

fetch with shared error handling
async function clog<T>(path: string): Promise<T> {
  const res = await fetch(`https://api.clog.dev/api/v1/external${path}`, {
    headers: { 'X-API-Key': process.env.CLOG_API_KEY! },
  });
  if (res.ok) return res.json() as Promise<T>;
  const err = await res.json().catch(() => ({ code: 'INTERNAL', message: res.statusText }));
  throw Object.assign(new Error(err.message), { code: err.code, status: res.status });
}

See Conventions → Error envelope for the full code list.

On this page