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 https://api.clog.dev/api/v1/external/posts/my-first-post \
-H "X-API-Key: clog_live_xxx"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 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 '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"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.
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 'https://api.clog.dev/api/v1/external/posts?authorId=0a4b3c2d-...&status=published' \
-H "X-API-Key: clog_live_xxx"curl 'https://api.clog.dev/api/v1/external/posts?categoryId=8e3f7c19-...&status=published' \
-H "X-API-Key: clog_live_xxx"curl 'https://api.clog.dev/api/v1/external/posts?categoryId=&status=published' \
-H "X-API-Key: clog_live_xxx"curl 'https://api.clog.dev/api/v1/external/posts?tagId=4d2e8a3c-...&status=published' \
-H "X-API-Key: clog_live_xxx"Search
q searches title, excerpt, and bodyText case-insensitively.
curl 'https://api.clog.dev/api/v1/external/posts?q=migration&status=published' \
-H "X-API-Key: clog_live_xxx"Create a post
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"
}'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 -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 -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 -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 -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 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 '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 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 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 '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 -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 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.
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.
Related
- Next.js worked example — same recipes, wired into an App Router project.
- Endpoint reference — every endpoint, generated from the OpenAPI spec.