Public API/Posts/Create or upsert post
POST/api/posts

Create or upsert post

Create a draft or published post, or upsert by slug with an account API key.

Primary write endpoint for the CLI and Obsidian plugin. Project is selected via X-Blogizi-Project, body fields, or automatically when the account has exactly one project. See Authentication.

Request

POST /api/posts Authorization: Bearer YOUR_ACCOUNT_API_KEY X-Blogizi-Project: your-project-slug Content-Type: application/json
{ "title": "How we built our auth flow", "slug": "how-we-built-our-auth-flow", "description": "≤160 char meta description", "keyword": "indie app authentication", "content": "Markdown body…", "status": "draft", "scheduledAt": "2026-10-26T09:00", "upsert": false }

Fields

  • title, slug — required
  • description, keyword, content — optional but recommended for SEO
  • coverImageUrl — optional public cover image URL (also accepted as frontmatter coverImageUrl / coverImage). On upsert, omit to keep the existing cover. Upload bytes with Upload post image first if needed.
  • ctaTitle, ctaSubtitle, ctaButtonLabel, ctaButtonUrl — optional per-post CTA override (same rules as Write in Markdown); on upsert, omit a field to keep the existing value
  • sidebarCtaTitle, sidebarCtaText, sidebarCtaBullets (string array), sidebarCtaButtonLabel, sidebarCtaButtonUrl — optional per-post sidebar CTA override (title, button label, and URL required together); omit to keep existing values on update
  • status — draft or published (published publishes immediately and cancels any schedule)
  • scheduledAt — optional ISO datetime (or naive local interpreted in the project timezone). Future → draft + schedule; null clears a schedule; omit leaves it unchanged on upsert. Response includes scheduledAt (UTC ISO); internal queue ids are never returned
  • upsert — when true, update an existing post with the same slug (Obsidian plugin pattern). Default create fails if the slug already exists.
  • projectSlug / project / projectId — optional if the header or sole-project rule already selects a blog

Inline images in content

Use standard Markdown after uploading a content image:

![Architecture diagram](https://media.blogizi.com/content/<postId>/<timestamp>.png)

Flow: create/upsert the post → upload with type=content → upsert again with the markdown snippet in content.

Response

Returns the post payload plus { project: { id, slug }, created: boolean }. Status is 201 on create and 200 on upsert update. When scheduled, data.status stays draft and data.scheduledAt is a UTC ISO string.

{ "data": { "slug": "how-we-built-our-auth-flow", "status": "draft", "scheduledAt": "2026-10-26T13:00:00.000Z" }, "project": { "id": "665f…", "slug": "my-app" }, "created": true }

Prefer CLI publishing for local markdown files. Use this endpoint when building your own client or when the Obsidian plugin upserts by slug.