Search documentation

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

Core concepts

Workspaces, memberships, content entities, and the headless delivery model.

Clog is a headless, multi-tenant blog CMS. You author content in a dashboard and deliver it through a JSON API. There is no public-facing blog rendered by Clog — consumers fetch content and render it on their own infrastructure (a marketing site, an app, a docs portal).

This page introduces the concepts you'll meet on every other page: Workspace, Membership, authentication, the content model, and the headless delivery layer.

Workspace = blog

A Workspace is a single blog and the tenant boundary in Clog. Everything else — posts, pages, categories, tags, authors, media, members, API keys — is scoped underneath one workspace. A user can create and belong to many workspaces; there is no level above (no parent "Organization") and no level below (one workspace is one blog).

Workspace slugs are unique platform-wide. Every other slug (post, page, category, tag, author) is unique only within its workspace, so two workspaces can both have a /about page or a news category.

Membership and permissions

You join a workspace through a Membership. Every membership carries:

  • A role — either owner (one per workspace, holds every permission implicitly) or member.
  • A list of permissions — explicit grants for members. Ignored for owners.

The permission vocabulary is a fixed, closed set:

ScopeReadWrite
Postsposts:readposts:write
Pagespages:readpages:write
Categoriescategories:readcategories:write
Tagstags:readtags:write
Authorsauthors:readauthors:write
Mediamedia:readmedia:write
Membersmembers:readmembers:write
API keysapi-keys:readapi-keys:write
Settingssettings:readsettings:write
SEOseo:readseo:write

The seo:* scopes are cross-cutting: they let a holder edit the SEO meta group (title, description, canonicalUrl, focus keyword, robots, OG, Twitter) on every entity without needing the entity's own :write scope. Editing the body or other content fields still requires the entity-specific write.

Owner-only actions cannot be granted to a member: deleting the workspace, transferring ownership, and editing the current owner's permission list. To strip owner power, transfer ownership first.

Authentication

The external API at /api/v1/external/* is authenticated with a workspace-bound API key sent in the X-API-Key header.

API keys are bound to one workspace AND one creator user. They carry no permissions of their own — at request time, the key inherits the creator's current Membership permissions on that workspace.

Revoking a member's Membership instantly disables every API key they created on that workspace — without touching the ApiKey rows. For machine integrations, issue keys from a dedicated service user so a teammate leaving doesn't break production.

The full header contract and error semantics live in API → Authenticating requests.

Content entities

Every content entity is workspace-scoped. The shapes you'll fetch from the external API:

EntityWhat it is
PostBlock-based article. Status flow draft → published → archived. Carries a bodyJson (array of typed blocks), derived bodyText / tocItems / readingTimeMin, an seo meta group, a schemaType plus structuredData for Schema.org variants, and an embedded author / category / featuredMedia / tags.
PageSimpler than a post — block body, status limited to draft | published, no categorization, no author. Has a pageType (WebPage, AboutPage, ContactPage, LearningResource, MedicalWebPage).
BlogCategoryHierarchical (one optional parent), workspace-scoped, with a materialised path for breadcrumbs.
TagFlat, workspace-scoped.
AuthorPersona record for post bylines. Not linked to platform users — just a displayName, bio, avatarMedia, and a person JSON-LD scaffold.
MediaA file stored in object storage. Public url, MIME type, optional alt / caption.

Post and Page bodies are arrays of blocks — typed objects discriminated by a type field. v1 ships 13 block types:

key_takeaways, paragraph, heading, list, quote, divider, image, video_embed, tweet_embed, callout, code, faq, cta.

See API → Rendering content and Reference → Block types for the full per-block schema.

Headless delivery

Clog stores content once and returns it shaped for consumers. A single GET /external/posts/:idOrSlug delivers everything you need to render an article:

  • The block array (bodyJson), with image blocks pre-enriched with a resolved public url — no follow-up media lookups.
  • The embedded author (and its avatarMedia), category (with the breadcrumb path resolved), featuredMedia, and tags.
  • The seo meta group plus a pre-assembled jsonLd Schema.org graph — drop it into a <script type="application/ld+json"> tag.
  • For posts with structured content (recipes, how-tos, reviews, events, ...): a derived structuredBlocks array in the same 13-block vocabulary, so you render the recipe card or how-to list with the same block renderer you already use for bodyJson.

Data is entered once in the dashboard and served three ways from one source — your reader UI (blocks), the crawler view (JSON-LD), and an optional typed payload if you want to build a bespoke widget.

Base URL and routes

The external API lives under /api/v1/external/* at https://api.clog.dev. Clog is a hosted service — there's no self-installed variant, and the base URL is the same for every consumer. Which workspace you read from is decided by the API key in the request, not by the host.

https://api.clog.dev/api/v1/external/posts
https://api.clog.dev/api/v1/external/posts/my-first-post
https://api.clog.dev/api/v1/external/redirects

All external routes are flat: the workspace is implicit from the bound API key, so paths never carry a :workspaceId segment. Single-resource routes accept either a UUID (resolved by id) or any other string (resolved by slug) — that's the idOrSlug convention you'll see throughout the reference.

Where to next

On this page