Writing content
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:
POST /poststo create the draft, you get back itsidand finalslug- optionally
PATCH /posts/{id}to refine it POST /posts/{id}/publishto make it visible
The author of any post created through the API is the blog owner.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /v1/blogs/{subdomain}/posts | Create a draft |
PATCH | /v1/blogs/{subdomain}/posts/{id} | Update a post (partial) |
POST | /v1/blogs/{subdomain}/posts/{id}/publish | Publish a post |
POST | /v1/blogs/{subdomain}/posts/{id}/unpublish | Revert a post to draft |
POST | /v1/blogs/{subdomain}/media | Upload 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"
}'
| Field | Required | Description |
|---|---|---|
title | yes | Post title, 255 characters max |
content | yes | Post body, in Markdown |
slug | no | Generated from the title when omitted |
excerpt | no | Short teaser text |
coverImageUrl | no | Cover image of the post. Upload the file first (see below) and pass back the URL you get, or point to any public URL |
tags | no | Tag names, created on the fly |
accessMode | no | FREE (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.
onConflict | Slug already taken |
|---|---|
DEDUPLICATE (default) | The slug is suffixed (-2, -3…) and a new post is created, 201 |
UPDATE | The 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.