API reference/Blog
Write a blog post
Try itWrites a new post. status=published puts it on the live site at once (posts don't wait for publish_site); draft keeps it hidden. Refused when the site's package has no blog.
POST
/api/v1/sites/{site_id}/postsAuthentication
An API key as Authorization: Bearer or x-api-key. It needs the blog:write scope — write, publish and delete blog posts (live at once).
blog:writeChanges the live siteIdempotency-Key requiredMCP tool: create_post
Parameters
Named placeholders go in the path; the rest in a JSON body.
site_idstringPath · required- The site's id.1 to 100 characters
titlestringBody · required- The headline.2 to 160 characters
slugstringBody · optional- The web address after /blog/. Omitted = made from the title.At most 80 characters
excerptstringBody · optional- The summary on the blog's list. Omitted = the first lines of the body.At most 400 characters
bodystringBody · required- The post as plain text: blank line = new paragraph, "## " heading, "- " list item, "> " quote, a picture address alone on a line.At most 60000 characters
category_idstring | nullBody · optional- A topic id (List blog topics); null for none.At most 100 characters
cover_image_urlstring | nullBody · optional- A /media/… address from the picture library (Upload a picture), or null.At most 500 characters
author_namestring | nullBody · optional- At most 80 characters
seo_titlestring | nullBody · optional- The post's own Google title (the blue link). null = the title.At most 70 characters
seo_descriptionstring | nullBody · optional- The post's own Google description. null = the excerpt.At most 300 characters
og_image_urlstring | nullBody · optional- A /media/… address shown when the post is shared. null = the cover picture.At most 500 characters
statusstringBody · optional- published = on the live site at once.One of: draft, publishedDefault
"draft" publish_onstring | nullBody · optional- The date shown on the post (YYYY-MM-DD). Omitted when publishing = today, or the date it was first published.
Response
201 with JSON on success (schema on the right). Errors use the standard envelope; a retry with the same Idempotency-Key gets the first answer back (how retries work).
curl -X POST 'https://websites.workflowcorp.com/api/v1/sites/SITE_ID/posts' \
-H "Authorization: Bearer $WEBSITEBUDDY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"title":"Spring hours are here","body":"We're open until 9 on Fridays and Saturdays from April.\n\n## What's new\n- Strawberry pie is back\n- Patio seating","status":"published"}'{
"site_id": "SITE_ID",
"title": "Spring hours are here",
"body": "We're open until 9 on Fridays and Saturdays from April.\n\n## What's new\n- Strawberry pie is back\n- Patio seating",
"status": "published"
}{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"slug": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"published"
]
},
"excerpt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"body": {
"type": "string",
"description": "The post as plain text with the console's marks (## heading, - list, > quote, a picture address on its own line)."
},
"category": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"cover_image_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"author_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"seo_title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The post's own Google title — null = the title is used."
},
"seo_description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The post's own Google description — null = the excerpt is used."
},
"og_image_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The picture shown when the post is shared — null = the cover picture."
},
"published_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The post on the live site — null while it's a draft."
},
"is_sample": {
"type": "boolean"
},
"created_at": {
"type": "string"
},
"updated_at": {
"type": "string"
}
},
"required": [
"id",
"title",
"slug",
"status",
"excerpt",
"body",
"category",
"cover_image_url",
"author_name",
"seo_title",
"seo_description",
"og_image_url",
"published_at",
"url",
"is_sample",
"created_at",
"updated_at"
],
"additionalProperties": false
}