Blog (Headless CMS) Integration

Zuletzt aktualisiert: May 6, 2026Abschnitt: General

Blog (Headless CMS) Integration

Full API reference: https://api.mailoo.app/docs/v1

Use Mailoo as a central headless CMS for your blog: create and manage articles in the dashboard and deliver them via an external API by project. External sites (or your own) can consume the same API.

Overview

The Blog integration works like other Mailoo integrations (e.g. Form): you add a Blog integration to a project. All articles for that blog belong to that integration. The API does not provide access to blog articles without authorization. Read API: list articles and get one by slug; requires X-API-Key with scope blog.external-read; access is keyed by project UID and integration ID.

Capabilities:

  • Create, edit, and delete articles in the Mailoo dashboard (per integration)
  • Publish as draft or published; optional excerpt, classifiers (themes, intents, audiences, SEO clusters), tags
  • Read API (X-API-Key required): list published articles and get one by slug, keyed by project UID and integration ID
  • Article response (list): id, title, slug, excerpt, content (Markdown, source of truth), htmlContent (HTML generated from content by the API; use for display), status, publishedAt, createdAt, updatedAt, author (id, name, email?, avatar, bio --- localized when locale is set), category (primary theme for backward compatibility: first theme by slug, or null), themes, intents, audiences, seoClusters (each an array of { id, slug, name }), featured, readTimeMinutes, tags (array, default []). Pagination includes page, limit, total, totalPages, hasNextPage, hasPrevPage. Inline images use URLs from the Mailoo image API (see images{.interpreted-text role="doc"}); there is no separate top-level article image field.
  • Article response (single by slug): same fields as the list item, plus optional linkedLinks --- an ordered array (max 10 per article) of curated links: id, type (internal_article | update_announcement | external_resource), label, url, intro (nullable), date (nullable ISO datetime), sortOrder. Internal links use a same-site path /blog/{targetSlug} in url; prefix with your locale segment when building the public href (e.g. /{locale}/blog/...). Editors manage links in the dashboard; internal_article rows can auto-fill label/date/intro from the target article with optional overrides.

Author profiles (dashboard)

Each account can maintain user-owned author profiles (display name, slug, optional public email, avatar URL, bio, optional per-locale overrides). Articles store a live reference to the selected profile so updates propagate. Pseudonyms are supported (isAlias). One profile can be marked as your global default; you can also set a per--blog-integration default (your preference only --- not stored in shared integration config).

  • Authors tab on the Blog integration: list and create/edit profiles.
  • Connection & settings tab: Default author saves the per-integration override (falls back to the global default when unset).
  • New/Edit article: choose an author profile or leave integration / global default so the API resolves the author automatically.

Creating a Blog Integration

  1. Go to Dashboard → Projects → [Your Project]
  2. Click Create New Integration
  3. Select Blog (Headless CMS)
  4. Set a name and status (e.g. Active)
  5. After creation, open the integration to see the Articles list and Connection block

Managing Articles

On the integration page you can:

  • Articles table: Title, slug, classifiers summary, status, date, and Edit for each article; quick filters by theme, intent, audience, and SEO cluster; Export Markdown report (with or without full article text) for AI-assisted content planning
  • Create article: Opens the new-article form (title, excerpt, content, status). Content is entered in Markdown (headings, lists, bold, links, images via the dashboard image panel or public embed URLs); the API converts it to HTML on save.
  • Edit: Opens the edit form for that article (save as draft, publish, or archive). Same Markdown editor for the default locale and for each translation (locales).
  • Related links: On create/edit, you can attach up to 10 curated linked links (type, URL or internal target article, optional label/intro/date). The external get by slug API exposes them as linkedLinks for headless frontends (e.g. release notes on a portfolio project page).

Content format: Article body is stored as content in Markdown. The API generates htmlContent from it. Consumers should use htmlContent for display to get correct structure and typography.

Links in htmlContent: Absolute http:// / https:// URLs and protocol-relative //… links include target="_blank" and rel="noopener noreferrer". Same-tab behavior applies to site-root paths (/path), explicit path-relative links (./…, ../…), in-page anchors (#section), and query-only URLs (?q=1).

Same-site links --- use a leading slash. In Markdown, [label](support/docs) becomes HTML href="support/docs". That is a path-relative URL: browsers resolve it per RFC 3986 against the directory of the page that displays the article, not against the site root. For example, on a post at https://example.com/en/blog/my-post, support/docs resolves to /en/blog/support/docs. To point at a site section from the root, write [label](/support/docs) (or the full https://… URL). Single-segment links without a slash (e.g. [other](other-slug)) are path-relative too, so they stay under the same directory as the post URL --- useful for sibling posts only when your public post URLs share that path prefix.

Mermaid in htmlContent: Fenced code blocks labeled mermaid are converted to static inline SVG (inside <figure class="mailoo-mermaid">). No browser-side Mermaid runtime is required; the same HTML is suitable for email-style rendering. Invalid Mermaid syntax causes the API to reject the save (articles, localized content, campaigns, etc.).

Managing themes (categories) and classifiers

Blog content is organized on four classifier axes, each with many-to-many links to articles:

  • THEME (replaces the former "category" model): pillar topics; the dashboard Categories block still manages theme values for this integration (same CRUD paths as before: …/categories).
  • INTENT, AUDIENCE, SEO_CLUSTER: optional axes for editorial and SEO alignment (e.g. informational vs transactional intent, ICP, query clusters). Manage values under GET/POST …/blog-classifier-values?type=… (dashboard / Bearer API).

SEO words (localized phrases) --- optional per integration: short phrases for SEO planning, not stored on articles. Each SEO word has a URL-style slug, a canonical English word, optional locales overrides (same JSON shape idea as article translations), and a many-to-many link to SEO_CLUSTER classifier values only. Dashboard Products tab: GET/POST /api/v1/projects/{projectUid}/integrations/{integrationId}/blog-seo-words and PUT/DELETE …/blog-seo-words/{wordId} (Bearer). List supports optional ?clusterId= (CUID of an SEO cluster value). When creating or editing an article, the form shows read-only the union of SEO words linked to the SEO clusters selected on that article.

When creating or editing an article, pick any number of values per axis. External list filters support category / categoryId for themes plus intentId, audienceId, seoClusterId (CUID of a classifier value).

Theme description (optional): For a theme value, choose an article as descriptionPostId (same as the old category description). External GET …/categories/slug/{slug}/description still resolves theme slugs.

System themes (about / portfolio)

Mailoo reserves two theme slugs per Blog integration: about and portfolio. Use them for standalone pages that should not appear in the default public article list.

  • Provisioning: Creating a Blog integration automatically creates theme values About (slug about) and Portfolio (slug portfolio).
  • Default list: GET …/blog/{projectUid}/integrations/{integrationId} with no category and no categoryId omits published articles linked to a theme whose slug is about or portfolio. Articles with no theme classifiers but a legacy category string matching those slugs (case-insensitive) are also omitted.
  • Explicit filter: Pass category (theme slug or legacy string) or categoryId (theme value id) to include those articles.
  • Dashboard article list shows all articles unless you apply filters.

The category query parameter filters by theme BlogClassifierValue (type THEME) slug or the legacy post category string. If both category and categoryId are set, both conditions apply (logical AND).

Dashboard Markdown report

Authenticated project editors can download a structured Markdown report for a Blog integration:

GET /api/v1/projects/{projectUid}/integrations/{integrationId}/blog/report?includeFullText=true|false&format=md&publishedFrom=YYYY-MM-DD&publishedTo=YYYY-MM-DD

Query parameters:

  • includeFullText --- when true, each article section includes the full Markdown body.
  • format --- only md is supported.
  • publishedFrom / publishedTo (optional, inclusive, UTC calendar dates YYYY-MM-DD) --- filter by publishedAt. When either is set, only posts with a non-null publishedAt are included (drafts without a publish date are excluded). If both are set, publishedFrom must be on or before publishedTo.

The dashboard Export report flow opens a dedicated page (breadcrumb navigation) to pick month, quarter, or a custom range, preview the Markdown, and save it to a file.

The file includes metadata, aggregates per classifier axis, a theme×intent matrix, and a per-article section (optionally with full Markdown bodies). Intended for prompting external LLMs for content planning.

Connection (External API)

The Connection block on the integration page shows the endpoints to use from your site or app. For web applications, the only recommended approach is BFF (API key on the server); direct API calls with X-API-Key are for server-to-server use. For Next.js, see blog-nextjs-example{.interpreted-text role="doc"}.

BFF routes (Next.js, same-origin) --- use env vars MAILOO_BLOG_PROJECT_UID, MAILOO_BLOG_INTEGRATION_ID, MAILOO_BLOG_API, MAILOO_BLOG_API_KEY:

  • List published articles: GET /api/v1/blog Query: page, limit, category, categoryId, intentId, audienceId, seoClusterId, search, locale, featured, tag (optional); filters combine with logical AND. Without category and categoryId, articles in reserved theme slugs about and portfolio are excluded (see System themes above). tag is an exact match on one value in the article tags array (case-sensitive as stored). Returns { success, data: [...], pagination } with hasNextPage, hasPrevPage.

  • Get one article by slug: GET /api/v1/blog/slug/{slug} Query: locale (optional). Returns { success, data: article }. Only published articles are returned.

  • List categories (themes): GET /api/v1/blog/categories Returns { success, data: Category[] } (id, slug, name) --- theme classifier values only (backward-compatible path name).

  • Get category description by slug: GET /api/v1/blog/categories/slug/{slug}/description Query: locale (optional). Returns { success, data: { description, descriptionHtml } }. If category has no description article, both fields are empty strings. If category slug does not exist, returns 404.

  • Public author profile (by id or slug): same-origin BFF is not required; call the API directly with X-API-Key:

    GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId}/authors/{authorSlugOrId}

    Query: locale (optional). Returns localized name, bio, avatar, and email only when the profile exposes a public email (pseudonyms do not leak the account email).

Direct API calls --- require X-API-Key header and scope blog.external-read:

  • List: GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId} Optional query: page, limit, category, categoryId, intentId, audienceId, seoClusterId, search, locale, featured, tag (same semantics as the BFF list route above).
  • Single: GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId}/slug/{slug} Query: locale (optional).
  • Categories: GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId}/categories
  • Category description: GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId}/categories/slug/{slug}/description Query: locale (optional). Same behavior as BFF endpoint above.
  • Author profile: GET {baseUrl}/api/v1/blog/{projectUid}/integrations/{integrationId}/authors/{authorSlugOrId} Query: locale (optional). Public read of a project owner's author profile for bylines and author pages.

Base URL is your Mailoo API (e.g. https://api.mailoo.app). projectUid and integrationId are shown in the Connection block. Images in article bodies use the Mailoo image upload API and public embed URLs inside content / htmlContent (see images{.interpreted-text role="doc"} for storage, scopes, and how to read bytes with Bearer or X-API-Key). Tags are returned as an array; empty array when none. Articles used as category descriptions are excluded from the external article list, so they are not duplicated in regular feed results. The same default list also excludes about and portfolio category articles unless you pass an explicit category filter. For full response fields and error codes, see the API docs above.

For a full Next.js example, see blog-nextjs-example{.interpreted-text role="doc"}.