Edit and refine posts
Find a brand's blog posts and revise their title, body, slug, generated text, or hero image.
Once a run on Create has written posts, or you've drafted them another way, these tools list the brand's blog posts and revise them: title, body, URL slug, a full text regeneration, or a new hero image.
When to use
Use these tools to iterate on existing blog posts before or after publishing. Start with list_blog_posts to find a postId, then get_blog_post to read the current body, then apply the edit you need. Reading, title/body/slug edits, and setting an external hero image are synchronous and free; regenerating text or generating an image is async and counts toward your plan's usage.
Editing a published post's text or image is a two-step consideration: update_blog_post edits the title, body, and/or slug in place and preserves published status, but regenerate_blog_post and generate_blog_post_image reject published posts (they reset the post to a draft). To rewrite a live post, move_blog_post_to_draft → regenerate → publish_blog_post.
Blog posts only (MEE-1995): every tool here operates on blog posts. A postId for any other kind of post is rejected.
Typical sequence
list_blog_posts— find thepostIdto iterate on.update_blog_post— quick in-place fixes to the title, body, and/or slug, orregenerate_blog_post— rewrite the body from the stored outline, and/orgenerate_blog_post_image— refresh the hero image.- Re-list to confirm the changes landed, then publish.
Tools
list_blog_posts
List a brand's blog posts for an agent. This is the discovery tool: use it to find a postId before editing, regenerating, or publishing.
Deprecated alias:
list_postsis still accepted butlist_blog_postsis the canonical name.
Authentication: Required
Type: Read-only.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent ID whose posts to list. Use get_account to find your agent IDs. |
limit | number | No | Max posts to return, newest first. Default 25, max 50. The response includes totalCount so you can tell when there are more. |
Returns up to limit blog posts, newest first, plus count (returned) and totalCount (all blog posts for the agent). Each post includes postId, title, slug, postUrl (nullable), platform (always blog), status, generationStatus, mediaId (nullable), mediaUrl (nullable), views (lifetime), and a linked workflow (id + name, nullable): the run or, for older posts, the worksheet that wrote it.
postUrl is the live, canonical public URL to share — it honors a verified custom domain, proxy hosting, or the default site host, so always use it rather than assembling a URL yourself. It is populated only when the post is actually publicly reachable (published, fully generated, not a synced copy, on a public site that isn't feed-hidden); a draft, scheduled, private, or feed-hidden post keeps its slug but returns postUrl: null. When Pendium doesn't host the brand's site, postUrl points to the address the site is actually served at, using the path the site gives that post. It is null when that site isn't live yet (including a newly verified domain that isn't serving the site yet), when Posts are turned off, or when a page already uses the post's address. mediaUrl is a directly-fetchable link: stock-library and uploaded images are returned as-is; AI-generated media stored privately is a short-lived signed URL that expires after about an hour (re-list to refresh). It's null only when a post has no media yet or its media isn't ready.
Blog posts only (MEE-1995): returns the brand's blog posts.
get_blog_post
Read one blog post's full body and metadata. list_blog_posts returns metadata only; this is the read-body companion — use it to pull the actual content for reading, summarizing, or proposing edits.
Authentication: Required. Available on any plan.
Type: Read-only.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent the post belongs to. |
postId | string | Yes | The post to read. From list_blog_posts. |
Synchronous, no LLM call. Returns the post's title, slug, status, generationStatus, description, mediaUrl, mediaSource, timestamps, and body — the body is plain markdown, the same string the public blog renders and that update_blog_post writes to. If neither the edited body nor a generated outline exists yet, body is null and the post is still pre-generation. mediaUrl is a directly-fetchable hero image link resolved the same way as list_blog_posts (stock/uploaded heroes as-is, AI-generated media as a short-lived signed URL), null when the post has no ready media.
Blog posts only (MEE-1995): a postId for any other kind of post is rejected.
update_blog_post
Edit a blog post's title, body, and/or URL slug in one call. Pass any combination of title, body, and slug; omitted fields are left unchanged, and at least one must be provided.
Deprecated aliases: the former granular tools
update_blog_post_title,update_blog_post_body, andupdate_blog_post_slug(and the older short namesupdate_title,update_caption,update_slug) still work — they route toupdate_blog_post. Their legacy field namescaption(→body) andrequestedSlug(→slug) are also still accepted.
Authentication: Required. Available on any plan.
Type: Write — non-destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent the post belongs to. |
postId | string | Yes | The post to edit. From list_blog_posts. |
title | string | No | New title. Appears in every listing and index. |
body | string | No | New body / main text for the post. An FAQ's hidden end marker is restored automatically when the FAQ is unchanged (see below). |
slug | string | No | New URL slug: lowercase letters, numbers, and hyphens only (e.g. my-new-post-url). |
Synchronous — writes the change and returns immediately. No LLM call. Each field refreshes the right public surface: a title change fans out across every listing and index; a body change refreshes the post's own page; a slug change refreshes the listings and 308-redirects the old URL to the new one. Changing the title does not change the slug — pass slug too if you want the URL to change. A slug is rejected when the post isn't a blog post, the slug is invalid (anything other than lowercase letters, numbers, and hyphens), or another post on the same agent already uses it. The response includes an updated array naming which fields changed, plus oldSlug/newSlug when the slug changed.
FAQ blocks: a post's FAQ block (written in the editor) ends with a hidden marker, and get_blog_post returns the body without it. When you send a body back, Pendium puts the marker back after each FAQ. Rewording the answers keeps it. The marker is left out only when one of these changed: the FAQ's questions or their order, the number of paragraphs in its last answer, or the paragraph or heading right after the FAQ. Without the marker, a paragraph right after the FAQ may be read as part of its last answer in the page's FAQ data. A marker line you include yourself ([//]: # (faq-end) on its own line) is saved as written.
regenerate_blog_post
Re-run text generation for one existing post. Pulls from the post's stored outline or source content and rewrites the body. Research saved to the post from the editor's Research the web or Search your knowledge is used too, with its source and date limits. Outside pages provide background. Before drafting and quality review, saved owner assertions and Business Info in cached research are compared with the current owner records. A removed or replaced assertion does not remain active just because it appeared in an earlier search. Historical edited prose is retained for wording constraints, rather than treated as verified business facts.
Deprecated alias:
regenerate_postis still accepted butregenerate_blog_postis the canonical name.
Authentication: Required.
Type: Write — non-destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent the post belongs to. |
postId | string | Yes | The post to regenerate. From list_blog_posts. |
Returns immediately after enqueueing; generation is async. The post's status flips to generating and new text lands in roughly 30–90 seconds. The post must already have a stored outline or source content (posts Pendium wrote do). Poll list_blog_posts to see when it completes.
Published posts are rejected. Regeneration resets the post to a generating state and the worker returns it as a draft, so regenerating a live article would pull it off the public site. To edit a published post's body in place use update_blog_post (preserves published status); to rewrite from scratch, move_blog_post_to_draft → regenerate_blog_post → publish_blog_post.
Blog posts only (MEE-1995): a postId for any other kind of post is rejected.
generate_blog_post_image
Generate — or regenerate — the hero image and media for one post.
Deprecated alias:
generate_post_imageis still accepted butgenerate_blog_post_imageis the canonical name.
Authentication: Required.
Type: Write — non-destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent the post belongs to. |
postId | string | Yes | The post to generate an image for. From list_blog_posts. |
Enqueues image generation and returns immediately — generation is async. If media generation is already running for that post, the call is a no-op success (alreadyInProgress: true), so concurrent calls are safe. A post sitting in a media_failed state is reset and retried. A finished draft gets a new header image from the same source as its current one (another of your brand photos or a stock photo it hasn't used, or a new AI image); it stays a draft, and if there's no other image to use or the attempt fails, it keeps the one it has. A post with a photo you added yourself is refused: pick another in the editor. Poll list_blog_posts for the resulting mediaUrl.
Published and scheduled posts are rejected. A new image returns the post to draft, so generating one for a live article would pull it off the public site, and a scheduled one would lose its schedule. To swap a published post's image, move_blog_post_to_draft → generate_blog_post_image → publish_blog_post.
Blog posts only (MEE-1995): a postId for any other kind of post is rejected.
set_blog_post_image
Set a blog post's hero image to an external, already-hosted image URL. Use this to point the hero at a specific image you host elsewhere, instead of generating one.
Authentication: Required. Available on any plan.
Type: Write — non-destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
syntheticId | number | Yes | The agent the post belongs to. |
postId | string | Yes | The post whose hero image to set. From list_blog_posts. |
imageUrl | string | Yes | A durable, publicly reachable https URL for the hero image, on a renderable host (see below). |
Synchronous, no LLM call. The image is referenced, not re-hosted, so pass a durable URL — one that later 404s breaks the post's hero. Only works on blog posts whose current image is a stock photo or a prior upload; posts with an AI-generated image are not replaceable this way (use generate_blog_post_image to re-run those instead). The change is live in Pendium immediately.
Renderable hosts only. Heroes are served through Next.js <Image>, which only loads image hosts allowlisted in the app's next.config.mjs remotePatterns. Prefer a Pendium-hosted blob URL (e.g. *.public.blob.vercel-storage.com) or an already-allowlisted CDN (Google user-content, images.pexels.com). An image on an unlisted host is accepted but renders broken on the public page.
Blog posts only (MEE-1995): a postId for any other kind of post is rejected.