Search documentation

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

Conventions

Base URL, response envelope, pagination, ordering, filtering, and the error format shared by every endpoint.

The external API is uniform: every list endpoint paginates the same way, every error has the same shape, and every single-resource route accepts a UUID or a slug. Learn these once and the rest of the reference reads itself.

Base URL

Every external route is prefixed with /api/v1/external/:

https://api.clog.dev/api/v1/external/posts
https://api.clog.dev/api/v1/external/pages/about
https://api.clog.dev/api/v1/external/redirects

Clog is a hosted service — https://api.clog.dev is the same base URL for every consumer. The workspace is implicit from the API key — paths never carry a :workspaceId segment.

Headers

HeaderWhenValue
X-API-Keyevery requestYour workspace-bound API key (clog_live_xxx). See Authenticating requests.
Content-Typewrite requestsapplication/json (or multipart/form-data for media upload).
Acceptoptionalapplication/json is the only supported response media type.

Resource identifiers — idOrSlug

Single-resource routes accept either a UUID (resolved by id) or any other string (resolved by slug), scoped to the bound workspace:

# By id
curl https://api.clog.dev/api/v1/external/posts/0a4b3c2d-... -H "X-API-Key: clog_live_xxx"

# By slug
curl https://api.clog.dev/api/v1/external/posts/my-first-post -H "X-API-Key: clog_live_xxx"

Whichever you pass, the response shape is identical.

Response envelopes

Single resource

A single-resource read or write returns the resource object directly — no wrapper:

{
  "id": "0a4b3c2d-...",
  "slug": "my-first-post",
  "title": "My first post",
  "bodyJson": [ /* ... */ ]
}

Paginated list

Every list endpoint returns a uniform PaginatedResponse:

{
  "data": [ /* items[] */ ],
  "page": 1,
  "pageSize": 10,
  "order": "desc",
  "orderBy": "publishedAt",
  "total": 42,
  "totalPages": 5
}
FieldMeaning
dataThe current page of items, typed to the endpoint's resource.
page1-based page number returned.
pageSizePage size applied.
orderasc or desc.
orderBySort key (omitted when no explicit sort was requested).
totalTotal matching items across all pages.
totalPagesTotal page count; minimum 1.

Pagination

All list endpoints accept the same four query params:

ParamDefaultNotes
page11-based page number.
pageSize10Items per page.
orderdescasc or desc.
orderByendpoint-specificMust be one of the endpoint's allowed sort keys; see the endpoint reference.

Example — second page of 25 published posts by newest first:

curl 'https://api.clog.dev/api/v1/external/posts?status=published&page=2&pageSize=25&order=desc&orderBy=publishedAt' \
  -H "X-API-Key: clog_live_xxx"

Allowed orderBy values vary per endpoint — e.g. posts accept createdAt | updatedAt | publishedAt | title | status | seoScore | readabilityScore, while media accepts createdAt | sizeBytes | mimeType. Each endpoint's reference page lists its allow-list.

Filtering

Filters are passed as query params. The available filters vary per endpoint — the reference pages enumerate them — but a few patterns recur:

PatternWhereBehaviour
q=<term>posts, pages, categories, tags, authorsCase-insensitive search across the entity's user-facing text fields (e.g. title, excerpt, bodyText for posts).
status=<value>posts, pagesRestrict by lifecycle status.
authorId, categoryId, tagIdpostsRestrict by FK. categoryId= (empty) filters to uncategorised.
dateFrom, dateTopostsISO datetime bounds on createdAt.
isActive=true|falseauthors, api-keysBoolean filter on the relevant active/inactive predicate.
mimeTypePrefix=image/mediaRestrict by a MIME prefix.

Error envelope

Every error response — regardless of status — returns the same JSON shape:

{
  "code": "BAD_REQUEST",
  "message": "Invalid post body: 'title' is required.",
  "details": { /* optional, see below */ }
}
FieldMeaning
codeMachine-readable error code. One of the values below. Use this for programmatic branching.
messageHuman-readable summary. Safe to log; not safe to expose to end users without translation.
detailsOptional extra context. Shape depends on the code (Zod validation tree, Prisma constraint info, per-row bulk errors, etc.).

Error codes

CodeHTTPTypical cause
BAD_REQUEST400Zod validation failure, invalid state transition, cross-workspace FK miss, fromPath === toPath on a redirect. details usually carries the Zod issues.
UNAUTHORIZED401Missing, unknown, revoked, or expired X-API-Key. Deliberately generic.
FORBIDDEN403Key is valid but the creator's membership doesn't grant the route's required permission.
NOT_FOUND404Resource doesn't exist in the bound workspace. (A slug present in another workspace is still a 404 here.)
METHOD_NOT_ALLOWED405The HTTP method isn't supported on this path.
PAYMENT_REQUIRED402Reserved. Not currently emitted by v1 endpoints.
CONFLICT409Duplicate slug, blocked delete due to references (e.g. deleting an author still attached to posts), duplicate redirect fromPath.
RATE_LIMITED429Global rate limit tripped. Back off and retry.
INTERNAL500Unhandled server error. Treat as transient; retry with backoff.

Handling errors

fetch with error handling
async function fetchPost(slug: string) {
  const res = await fetch(
    `https://api.clog.dev/api/v1/external/posts/${slug}`,
    { headers: { 'X-API-Key': process.env.CLOG_API_KEY! } },
  );

  if (res.status === 404) return null;          // post doesn't exist; treat as missing
  if (!res.ok) {
    const err = await res.json().catch(() => ({ code: 'INTERNAL', message: res.statusText }));
    throw new Error(`Clog ${err.code}: ${err.message}`);
  }

  return res.json();
}

Branch on code, not on the human-readable message — messages are not part of the API contract and may change without notice.

Date and time

All timestamps are ISO 8601 strings in UTC ("2026-05-23T12:34:56.789Z"). All dateFrom / dateTo filters accept the same format. There is no separate date-only type in v1.

Idempotency and retries

  • Reads (GET) are safe to retry — they don't mutate state.
  • Soft-delete on DELETE — deleting a post or page stamps deletedAt and frees the slug. The body returned is the pre-delete snapshot with deletedAt set; the row is hidden from all subsequent reads.
  • Tag attach / detach is idempotent: re-attaching an attached tag (or detaching a missing one) is a silent no-op.
  • Link-health reports (POST /external/link-health) upsert on (workspaceId, path) and bump hitCount — repeat reports for the same path are explicitly safe to send.

For other write operations, treat retries as you would any non-idempotent HTTP call: retry only after confirming the prior call did not commit, and prefer idempotent designs in your own code (e.g. by computing a deterministic slug client-side).

On this page