Your blog
Write posts from a short brief. Nothing goes live until you say so.
Overview
A good blog post answers a question your customers really ask, in the words they'd use. Your assistant can write those posts with you, and nothing goes live until you say so.
Give it a short brief, like the question and what you want to say. Or paste in a post you've already written. It checks the draft against the same rules as the rest of your site. Then it saves the draft and gives you a preview link to open on your phone. Ask for changes and it makes them.
When you're happy, tell it to publish. It publishes the exact version that passed the check. If anything changed since then, it checks again first. You can edit a live post the same way, like fixing a typo or swapping the photo. A change to a live post gets the same check before it saves.
To take a post down, your assistant sends us a request. There's also a daily limit on how many drafts it writes from a brief.
Changes here need an active account. If your account is on hold or cancelled, editing pauses until it's back.
You can also do this in your workspace. Look in Site, under Blog (/workspace).
The tools on this page
| Tool | Reads | Writes | Asks you first |
|---|---|---|---|
| See your blog | Yes | No | No |
| Write a draft | No | Yes | Yes |
| Check a post | Yes | No | No |
| Publish a post | No | Yes | Always |
| Edit a post | No | Yes | Always |
See your blog
get_blog_contextReads only
What you can ask
- What have we posted on the blog lately?
- What should we write about next?
What it does
Your blog posts, live and draft, and the services you can link to.
What your assistant sends
| Field | Required | Description |
|---|---|---|
limitinteger | No | Recent posts to include, default 20. |
{
"limit": 10
}{
"posts": [
{
"id": "55555555-5555-4555-8555-555555555555",
"title": "When to replace brake pads",
"slug": "when-to-replace-brake-pads",
"status": "published",
"published_at": "2026-09-12T14:00:00.000Z",
"category": "Brake pads"
}
],
"services": [
{
"id": "66666666-6666-4666-8666-666666666666",
"name": "Brake repair",
"path": "/services/brake-repair"
}
],
"post_shape": "post = { title (8-90 chars, the question the post answers), excerpt, tldr, body (markdown), faqs, category, tags[], service_ids[] }.",
"steps": [
"draft_blog_post with a brief or a whole post.",
"publish_blog_post with the post id, the latest validation_id and confirm.intent."
],
"voice_notes": {
"note": "Reference only, never checked.",
"data": [
"The owner prefers plain words over trade terms."
]
}
}Write a draft
draft_blog_postChanges your site or sends something
What you can ask
- Write a blog post about when to replace brake pads.
- Make the intro of that draft shorter.
What it does
Write a blog draft from a short brief or a post you wrote, or change a draft. Never publishes.
Your assistant asks you to confirm this first.
What it won't do
It writes drafts and never publishes. There's a daily limit on drafts from a brief, usually 10. Past that, your assistant is told when it can try again.
What your assistant sends
| Field | Required | Description |
|---|---|---|
briefobject | No | Have the writer draft it. Or send post, not both. |
postobject | No | A whole post. get_blog_context describes the shape. |
revise_post_idstring | No | A draft to revise, with post (replaces it) or notes (the writer revises it). |
notesstring | No | Revision instructions for the writer. |
owner_requeststring | No | What the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for. |
{
"brief": {
"topic": "When to replace brake pads",
"angle": "When to call us"
}
}{
"post": {
"id": "55555555-5555-4555-8555-555555555555",
"slug": "when-to-replace-brake-pads",
"title": "When to replace brake pads",
"excerpt": "Age, leaks and rising bills are the signs. Here is how to tell a repair from a replacement.",
"tldr": "Past 10 years with a leak, replace it.",
"body": "Most brake pads last 30,000 to 70,000 miles.\n\n## When to call us\n\nIf you are not sure, call and we will take a look.",
"faqs": [
{
"question": "How long does a brake pad last?",
"answer": "Most brake pads last 30,000 to 70,000 miles."
}
],
"category": "Brake pads",
"tags": [
"brake pad"
],
"seo_title": "When to replace brake pads | ESG",
"seo_description": "Age, leaks and rising bills are the signs. Here is how to tell a repair from a replacement.",
"status": "draft",
"service_ids": [
"66666666-6666-4666-8666-666666666666"
],
"hero": {
"status": "generating"
}
},
"updated_at": null,
"gate": {
"status": "pass",
"findings": [],
"validation_id": "v1.opaque"
},
"preview_url": "https://app.example.com/preview/blog/bp1.opaque",
"expires_in_minutes": 30,
"next": "Saved as a draft. Show the owner the preview; publish_blog_post with this validation_id when they approve."
}Check a post
validate_blog_postReads only
What you can ask
- Check my draft before I post it.
- Is this post ready to publish?
What it does
Check a draft or an unsaved post against the rules. Saves nothing.
What your assistant sends
| Field | Required | Description |
|---|---|---|
post_idstring | No | A saved draft. Its validation_id can publish it. |
postobject | No | Unsaved copy. Checked only; draft it before publishing. |
{
"post_id": "55555555-5555-4555-8555-555555555555"
}{
"post_id": "55555555-5555-4555-8555-555555555555",
"status": "pass",
"findings": [],
"validation_id": "v1.opaque"
}Publish a post
publish_blog_postChanges your site or sends something
What you can ask
- Publish the brake pad post.
- Put that draft live.
What it does
Publish a blog draft now.
This asks you first, every time.
What it needs
- The draft you want to publish.
- A passed check of that exact draft. Your assistant runs it first.
What it won't do
It publishes only the version that passed the check. If the draft changed since then, it checks again first. To take a post down, your assistant sends us a request.
What your assistant sends
| Field | Required | Description |
|---|---|---|
post_idstring | Yes | |
validation_idstring | Yes | From draft_blog_post or validate_blog_post for this draft as stored. |
publish_atstring | No | Not available yet; posts publish immediately. |
confirmobject | Yes | Required. One line stating the change, in the owner's words. |
idempotency_keystring | No | Optional. Reusing a key within 24 hours returns the first publish's result instead of publishing again. |
owner_requeststring | No | What the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for. |
{
"post_id": "55555555-5555-4555-8555-555555555555",
"validation_id": "v1.opaque",
"confirm": {
"intent": "Publish the brake pad post"
},
"idempotency_key": "publish-post-0930"
}{
"published": true,
"post_id": "55555555-5555-4555-8555-555555555555",
"slug": "when-to-replace-brake-pads",
"live_url": "https://esg.example/blog/when-to-replace-brake-pads",
"published_at": "2026-09-30T14:00:00.000Z",
"note": "Live now. Unpublishing or deleting it is a submit_request."
}Edit a post
update_blog_postChanges your site or sends something
What you can ask
- Fix the typo in the second paragraph of our last post.
- Change the photo on the brake pad post.
What it does
Edit a post's title, summary, body or main photo, live or draft.
This asks you first, every time.
What it needs
- The post you want to change.
What it won't do
A live post gets the same check as publishing. If your change fails it, you get the reason. It won't write over a newer edit someone else made.
What your assistant sends
| Field | Required | Description |
|---|---|---|
post_idstring | Yes | The post, from get_blog_context. |
titlestring | No | The new title. Omit to keep it. |
excerptstring | No | The new summary. Omit to keep it. |
bodystring | No | The new body, markdown. Omit to keep it. |
hero_media_idstring or null | No | A library photo (list_media) for the post's main photo, or null to remove it. Omit to keep it. |
expected_updated_atstring or null | No | The post's updated_at as you read it. A post changed since is refused (post_changed) rather than overwritten. |
confirmobject | Yes | Required. One line stating the change, in the owner's words. |
idempotency_keystring | No | Optional. Reusing a key within 24 hours returns the first change's result instead of writing again. |
owner_requeststring | No | What the owner asked for, in their words, one sentence. Optional. Stored with the audit record so their team can see what owners ask for. |
{
"post_id": "55555555-5555-4555-8555-555555555555",
"hero_media_id": null,
"confirm": {
"intent": "Take the photo off the brake pad post"
}
}{
"updated": true,
"post_id": "55555555-5555-4555-8555-555555555555",
"slug": "when-to-replace-brake-pads",
"status": "published",
"changed": [],
"hero": "removed",
"version": "2026-09-30T14:00:00.000Z",
"findings": [],
"live_url": "https://esg.example/blog/when-to-replace-brake-pads",
"message": "Saved. The live post shows the change."
}