Writing content

Create, update, publish and unpublish posts, and upload media, with the Writizzy public API.

The write side of the v1 API lets you manage your content from your own tools: publish from a Git repository, run an inbound migration script, or wire Writizzy into a custom editorial pipeline. It stays in the same /v1 contract as the read endpoints, no separate version.

Use a write key

Writing requires a write key. A read key can only read: any create, update, publish or upload request made with a read key returns 403. A write key includes read access, so a single script can fetch a post and then patch it.

Generate the write key from Blog Settings → Developer API, then pass it as a Bearer token like any other request:

POST /v1/blogs/yourblog/posts
Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The write flow

A post is always created as a draft. Publishing is a separate, explicit step. A typical flow is:

  1. POST /posts to create the draft, you get back its id and final slug
  2. optionally PATCH /posts/{id} to refine it
  3. POST /posts/{id}/publish to make it visible

The author of any post created through the API is the blog owner.

Endpoints

MethodPathDescription
POST/v1/blogs/{subdomain}/postsCreate a draft
PATCH/v1/blogs/{subdomain}/posts/{id}Update a post (partial)
POST/v1/blogs/{subdomain}/posts/{id}/publishPublish a post
POST/v1/blogs/{subdomain}/posts/{id}/unpublishRevert a post to draft
POST/v1/blogs/{subdomain}/mediaUpload a media file

Create a draft

title and content are required. slug is optional: when omitted it is generated from the title, and de-duplicated automatically if it collides with an existing post. Tags are created on the fly.

curl -X POST https://api.writizzy.com/v1/blogs/yourblog/posts \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting started with Kotlin",
    "content": "# Hello\n\nThis is my first post.",
    "excerpt": "A short intro to Kotlin.",
    "coverImageUrl": "https://cdn.writizzy.com/yourblog/kotlin.png",
    "tags": ["kotlin", "tutorial"],
    "accessMode": "FREE"
  }'
FieldRequiredDescription
titleyesPost title, 255 characters max
contentyesPost body, in Markdown
slugnoGenerated from the title when omitted
excerptnoShort teaser text
coverImageUrlnoCover image of the post. Upload the file first (see below) and pass back the URL you get, or point to any public URL
tagsnoTag names, created on the fly
accessModenoFREE (default) or PAID

The response is the full post, including the resolved slug and id.

All of these can be changed later with PATCH /posts/{id}.

Replaying a create: onConflict

By default a replayed create makes a second post. Add onConflict=UPDATE and the slug becomes the identity of the post instead: if it is already taken on your blog, the existing post is updated in place and the response is 200 instead of 201.

curl -X POST "https://api.writizzy.com/v1/blogs/yourblog/posts?onConflict=UPDATE" \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting started with Kotlin",
    "slug": "getting-started-with-kotlin",
    "content": "# Hello\n\nRevised body."
  }'

This is what makes a publishing pipeline simple: a script that pushes a Markdown file on every commit does not have to remember any id, and does not have to check whether the post exists first. Send the same slug, get the same post.

onConflictSlug already taken
DEDUPLICATE (default)The slug is suffixed (-2, -3…) and a new post is created, 201
UPDATEThe existing post is updated in place, 200

Fields absent from the payload are left unchanged, like a partial update. The publication state is never touched: publishing stays an explicit call. A slug held by a static page is left alone, and the create path runs as usual.

{
  "id": "p_abc123",
  "title": "Getting started with Kotlin",
  "slug": "getting-started-with-kotlin",
  "content": "# Hello\n\nThis is my first post.",
  "publishedAt": null,
  "accessMode": "FREE",
  "...": "..."
}

Update a post

Send only the fields you want to change, the rest are left untouched. This call never changes the publication state.

curl -X PATCH https://api.writizzy.com/v1/blogs/yourblog/posts/p_abc123 \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "excerpt": "A short intro to Kotlin." }'

Changing the slug after publication is discouraged, it breaks the public URL. A slug collision returns 400.

Publish and unpublish

Publishing makes the post visible. That is its only effect: no newsletter is sent and no cross-posting is triggered, those are separate, explicit actions. A future publishedAt schedules the post, and it defaults to today when omitted. Publishing an already-published post is a no-op.

curl -X POST https://api.writizzy.com/v1/blogs/yourblog/posts/p_abc123/publish \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Unpublishing reverts the post to a draft. It is reversible and safe, and a no-op on a draft.

curl -X POST https://api.writizzy.com/v1/blogs/yourblog/posts/p_abc123/unpublish \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Upload media

Upload an image as multipart/form-data and get back its public CDN URL. Use that URL as a post coverImageUrl or inside the Markdown content.

curl -X POST https://api.writizzy.com/v1/blogs/yourblog/media \
  -H "Authorization: Bearer wz_yourblog_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@cover.png" \
  -F "altText=Cover image"
{
  "url": "https://cdn.writizzy.com/blogs/.../cover.png",
  "filename": "cover.png",
  "size": 48213
}

Uploads count against your plan's media storage quota. A request that would exceed it returns 403.

Notes

  • The post type is always POST. Static pages are not editable through the API.
  • The author is the blog owner, there is no third-party author to associate.
  • Hard deletion is not exposed. Use unpublish to remove a post from the public site.
  • All write endpoints return the standard error format.