Get a released blog

GET /api/consumer/blogs/{blogId}

Returns one released blog with its full content payload — markdown, frontmatter, structured outline, and uploaded media assets — ready for rendering on your site.

Path parameters

NameTypeDescription
blogIdstringThe blog identifier returned as id from GET /api/consumer/blogs.

Headers

Same as the list endpoint: pass the workspace API key via x-api-key or Authorization: Bearer.

Response

200 OK with the released blog detail. 404 Not Found if the ID does not match a released blog in your workspace — either the ID is wrong, the blog was never released, or it belongs to a different workspace.

Response shape

type GetReleasedBlogResponse = {
  id: string;             // same as the blogId you requested
  releaseNotes: string | null;
  releasedAt: string;     // ISO 8601 timestamp
  version: {
    id: string;
    versionNumber: number;
    renderedTitle: string;
    renderedSlug: string;
    metaTitle: string | null;
    metaDescription: string | null;
    markdown: string;     // full article body
    frontmatter: Record<string, string> | null;
    structuredContent: unknown | null; // outline JSON, when available
    reviewSummary: string | null;
    updatedAt: string;
    assets: Array<{
      id: string;
      assetRole: "cover_image" | "inline_image" | "diagram" | "illustration";
      publicUrl: string | null;
      signedUrl: string | null;
      altText: string | null;
      caption: string | null;
      width: number | null;
      height: number | null;
      metadata: unknown;
    }>;
  };
};

Field notes

  • markdown is the canonical body. It already contains inline image references using the asset URLs below — render it with any standard CommonMark / GFM renderer.
  • frontmatter mirrors the YAML-style metadata the editor signed off on (e.g. tags, category). Keys and values are both strings.
  • structuredContent is a structured outline of the article (typed as unknown so the schema can evolve). Use it if you want to render the outline as a TOC; ignore it if you only need the markdown.
  • assets are uploaded media. Prefer publicUrl; fall back to signedUrl for buckets fronted by short-lived URLs. Filter by assetRole to find the cover image (cover_image is always at most one entry per release).

Markdown features used

The markdown field uses GitHub-Flavored Markdown (GFM) in addition to CommonMark. Pick a renderer that supports GFM (e.g. react-markdown with remark-gfm, markdown-it with the gfm preset, or any renderer GitHub itself uses). You may encounter:

  • Tables for comparisons and criteria.
  • Task lists (- [ ], - [x]) for checklists.
  • Strikethrough (~~text~~).
  • Footnotes ([^1]) for inline citations.
  • Fenced code blocks with language tags (```ts, ```bash, etc.). Pair your renderer with a syntax highlighter (Prism, Shiki, Highlight.js) if you want highlighted code.
  • Callout-style blockquotes that lead with a bold label (> **Note:** …, > **Tip:** …, > **Warning:** …, > **Example:** …). These render as plain styled blockquotes on any CommonMark renderer; if you want stronger per-type styling (icon, background colour) you can detect the leading <strong> text in the blockquote and theme accordingly.

A vanilla CommonMark renderer will degrade gracefully — tables fall back to plain text rows, task lists become regular bullets, footnotes appear inline. For the cleanest reading experience, use GFM.

Example

curl -sS https://api.contentpilot.uixlabs.co/api/consumer/blogs/ck1234567890 
  -H "x-api-key: $CONTENT_PILOT_API_KEY"
{
  "id": "ck1234567890",
  "releaseNotes": "First public release after editor sign-off.",
  "releasedAt": "2026-05-08T09:14:00.000Z",
  "version": {
    "id": "cv0987654321",
    "versionNumber": 1,
    "renderedTitle": "How ATS optimisation actually works in 2026",
    "renderedSlug": "ats-optimisation-2026",
    "metaTitle": "ATS optimisation in 2026",
    "metaDescription": "What today's ATS systems really score, and the practical levers that move the needle.",
    "markdown": "# How ATS optimisation actually works in 2026\n\nMost candidates...",
    "frontmatter": {
      "category": "career",
      "readingTime": "8 min"
    },
    "structuredContent": null,
    "reviewSummary": "Quality recommendation: ship.",
    "updatedAt": "2026-05-08T09:13:42.000Z",
    "assets": [
      {
        "id": "ca555",
        "assetRole": "cover_image",
        "publicUrl": "https://content-pilot.s3.ap-south-1.amazonaws.com/ck1234567890/cover.png",
        "signedUrl": null,
        "altText": "Recruiter screen showing ATS scoring",
        "caption": null,
        "width": 1600,
        "height": 900,
        "metadata": null
      }
    ]
  }
}

TypeScript helper

async function fetchReleasedBlog(blogId: string) {
  const response = await fetch(
    `https://api.contentpilot.uixlabs.co/api/consumer/blogs/${encodeURIComponent(blogId)}`,
    { headers: { "x-api-key": process.env.CONTENT_PILOT_API_KEY! } },
  );

  if (response.status === 404) return null;
  if (!response.ok) throw new Error(`Failed: ${response.status}`);

  return (await response.json()) as GetReleasedBlogResponse;
}

Updates and republishing

When an editor releases a new version of the same blog, the response under the same blogId updates in place — version.versionNumber increments and releasedAt advances. Treat the response as the current released version; use version.versionNumber and version.updatedAt to invalidate any cached copy on your side.

Errors

See Errors. On this endpoint specifically:

  • 401 Unauthorized — missing or invalid API key.
  • 404 Not Found — the ID is not a released blog in your workspace.
  • 500 Internal Server Error — retry with backoff.