/
Developer
Get a key
Knowledge base
Start here
Context
Topics & Planner
Self-serve setup
Phantom
Back to app
Interface →
Capability →
The grid
REST
Plain HTTP · the publish loop
670 tools
Set up →
MCP
The full surface, for agents
670 tools
Set up →
CLI
letterstory, in your terminal
670 tools
Set up →
Content
Read, draft, and publish articles: the words themselves.
49
all tools
49
all tools
49
all tools
Collections
A publication with one config file. run_collection sources, writes, gates and publishes one post.
44
all tools
44
all tools
44
all tools
Canvas & visuals
Canvas style templates, semi-structured compositions, stickers, and table/venn visuals.
21
all tools
21
all tools
21
all tools
Flows
Run editing passes over an article and read the results back.
2
all tools
2
all tools
2
all tools
Seers
Point a seer at a repo, Notion, a spec URL or a news query and turn what it finds into drafts. Connect with a GitHub PAT (set_github_connection) or a Notion secret (set_notion_connection).
28
all tools
28
all tools
28
all tools
Arenas
Research agents that turn a topic into a sourced, fact-checked outline, alone or before a kernel writes.
22
all tools
22
all tools
22
all tools
Writing kernels
Submit a topic/brief to an external writing kernel and get a finished draft written back.
40
all tools
40
all tools
40
all tools
Connectors
Push finished articles out to Webflow, Framer, or Google Docs.
32
all tools
32
all tools
32
all tools
Phantomstory
Sites Letterstory hosts as independent publications: create, configure, add a domain, rebuild. Publish with run_collection.
34
all tools
34
all tools
34
all tools
Mailers
Outbound email. Mailers: domains, warm-up, reply forwarding, suppression. Prospects: lists from CSV or a description. Sequences: multi-step emails with a review queue.
58
all tools
58
all tools
58
all tools
Strategy
Positioning, competitors, and onboarding that steer the writing.
105
all tools
105
all tools
105
all tools
Insights
Search Console performance and The Story's momentum feed. Connect Search Console first in Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations).
58
all tools
58
all tools
58
all tools
Shredder
Run outside content through shredder endpoints: each sentence is rewritten across LLM providers, structure untouched.
16
all tools
16
all tools
16
all tools
Ledes
Turn a prospect's context into scored cold-email opening lines from your editable library.
20
all tools
20
all tools
20
all tools
Landings
Landing pages and when to cite them. Collections with auto-mention link to them in new articles.
5
all tools
5
all tools
5
all tools
The Site
One model of a customer's whole marketing site keyed by host and path: map a host, walk the tree, read what was noticed, and propose, accept or dismiss changes.
37
all tools
37
all tools
37
all tools
Account
API keys, organization profile, branding assets, members, and pending invites.
99
all tools
99
all tools
99
all tools
The grid
Capability
49 tools
Content
Read, draft, and publish articles: the words themselves.
All 49 tools
POST
search_posts
Search posts by name/title in the caller's organization. All words must match. Returns 8 results unless `limit` says otherwise (20 at most), with their app links.
article:read
POST
list_articles
List recent articles in the caller's organization, newest-updated first. Pass collection_id (from list_collections) to only return that collection's articles, phase='finished' to find drafts ready for review but not yet published, phases for any of several phases, unpublished_only=true for every not-yet-published article regardless of which phase it's in, search to match titles, mine=true for the caller's own posts, and offset with limit to page (a letterbase's All posts).
article:read
POST
get_auto_pen_status
Poll the latest Auto-Pen run for an article: its status (running/completed/failed/cancelled), each step's state, the template it is composing with, and the error if it failed. It is also the run's whole record, so an agent can walk the chain: the research run (get_arena_run), the kernel job and its endpoint run (get_kernel_endpoint_run), the in-loop shred run, the verification run, the arena version and the kernel version (`Hazel@20`) that ran, and per step the run it made (`run`: primitive, id, version), the model it wrote with, and the post versions it read and left (`before` / `after`, article_history ids; null after = wrote nothing, absent on runs before 2026-10-03). Polling also nudges a running run forward, exactly as the app's panel does, so call this rather than waiting on the 5-minute sweep. Returns auto_pen: null when the article has never had a run.
article:read
POST
get_cover_regen_status
Poll the latest cover-regeneration job for a collection (started by regenerate_covers): its status, stage, progress tallies, and any failed article ids. Returns { job: null } if no sweep has ever run for the collection. Nudges the job's tick forward if it's still running.
article:read
POST
list_collection_authors
List a collection's author bank (the recurring authors its posts are bylined by) plus the current byline distribution (how many posts each author has). Use the author ids as targets for assign_collection_authors.
article:read
POST
get_article
Fetch a single article by id from the caller's organization — the LIVE (draft) copy, with its cover, byline, dek and tags flattened the same way get_published flattens them, its lifecycle_phase, and its `state`: where it is (empty · writing · ready · scheduled · published), that as a sentence, the next thing to do (`primary`: write · publish · publish_now · update_live, or null when nothing pressed would help) and what it is waiting on (the tool writing it, reviewers, a hold). Use get_published instead when you want the frozen published version.
article:read
POST
get_article_history
A post's versions, newest first: each version's title and body, what made it (`source`), who (`updated_by_user`; null for an unattended write) and when, and which version is live (`published_version`). Restore one with restore_article_version.
article:read
POST
export_article
Export a post's current draft as a file: `md` is Markdown with YAML frontmatter (title, description, author, date, tags) and hosted image URLs, returned as text; `pdf` is the rendered PDF, returned base64 (refused where PDF rendering is not configured). The app's Export menu and Copy as Markdown.
article:read
GET
list_published
List the published articles in the caller's organization, returning each article's frozen (published) content. Ordered newest-first by published_at (the first-publish time, so a republish does NOT move a post back to the top — compare published_version or updated_at to spot republished posts), with article_id as the tiebreaker. Paging is by cursor, not offset: when has_more is true, pass next_cursor back as cursor. Pass collection_id to scope to one collection, and format='md' to get Markdown instead of HTML.
article:read
POST
get_published
Fetch a single published article by article_id or slug, returning its frozen (published) content. Slugs are unique per collection, not per organization: a site serving one collection should pass collection_id with the slug, or a same-slug post from another collection in the org can come back instead. Every published article has a slug: the one set on the post, or else one derived from the published title — a derived slug changes if the title changes on republish, so set one explicitly (or key on article_id) if routes depend on it. Pass format='md' for Markdown instead of HTML.
article:read
POST
get_enhance_run
One enhance pass run: the pass, who asked (auto-pen, article, api, relink), the enhancer version and the model in each seat, the post versions before and after, exactly what it added (`applied.items`), the outcome (added / none / skipped with `skip_reason` / failed), tokens per model and time. An Auto-Pen step's `run.id` (get_auto_pen_status) with primitive `enhance` is one of these.
article:read
POST
list_enhance_runs
The org's enhance pass runs, newest first — filter by post, Auto-Pen, collection, pass or outcome, and page forward with `since`. For an agent watching an account: `outcome: "skipped"` finds collections whose sitemap cannot be read, `outcome: "failed"` passes that errored.
article:read
POST
ingest_article
Create a new draft article in the caller's organization, or update one in place by passing back its article_id (from a prior ingest_article call) instead of creating a duplicate. Pass custom_fields (set_collection_config) to carry over values your source already has -- e.g. FAQ, categories -- rather than have Letterstory generate them: a provided key is written as-is and is never generated, regardless of that field's write_policy; the custom-fields pass only fills in keys you didn't provide, as a backup. Pass context to steer research and to give the post your own data (benchmarks, metrics, customer results): figures in it are stated in the post with your attribution, never linked or softened. Returns the article id and title.
article:write
POST
run_custom_fields_pass
Run the custom-fields pass over one article (article_id; force bypasses the create_only guard) or sweep a whole collection (collection_id; up to `limit`/50 articles per call, oldest-touched first — call again while has_more is true to continue). Generates values for this collection's declared custom_fields (set_collection_config) and writes them onto articles.metadata.cf__<key>, the same keys get_published/list_published surface as custom_fields.<key>. New articles pick fields up automatically via Auto-Pen or ingest_article; this tool is for backfilling existing posts right after defining new fields.
article:write
POST
run_auto_pen
Run the collection's full Auto-Pen sequence over an existing draft: research the topic, write it with the writing kernel, then run the enhance passes the collection has on (visuals, internal links, external links, metadata; each recorded as an enhance run, get_enhance_run). Leave steps out of this run with skip_steps. THIS IS THE PIPELINE THAT READS A TEMPLATE. A collection's template_key (see update_auto_pen_config) and a post's own template override are resolved here and nowhere else, so this — not run_kernel with an article_id — is how a templated post gets written. run_kernel hands the article's current text to a kernel and resolves no plan, so a post written that way comes back unshaped however its collection is configured. The post must already be in a collection; a collection that has never configured Auto-Pen runs the default Research > Write > Enhance sequence. This writes a post that already exists — a person's own draft, or an explicit rewrite — so it opens no collection run: the collection's cadence and approval gate don't apply, and publishing stays a separate step. To have the engine write a NEW post into a collection, use run_collection (now) or queue_suggestion (the next slot). Asynchronous: returns immediately with the run's id and steps. A run advances on its own (a sweep drives it every 5 minutes) and get_auto_pen_status nudges it along, so poll that until status is 'completed' or 'failed'. Research plus a kernel run typically takes 10-40 minutes.
article:write
POST
cancel_auto_pen
Cancel the article's running Auto-Pen and unlock the post so another run (or a research/kernel run) can start. No-ops if nothing is running. Work already written to the article is left in place.
article:write
POST
assign_collection_authors
Batch re-byline a collection's posts (the author swap). mode='set' pins one author (author_id) to the targets; mode='auto' distributes across the active bank by each post's topics. Scope with article_ids or all=true. Writes metadata only (no republish, published dates untouched) and rebuilds the live phantom sites so the new bylines show.
article:write
POST
delete_article
Permanently delete one or more articles from the caller's organization: content, version history, and any published/live copy are all removed and this cannot be undone. Pass 1-100 article_ids. Returns which ids were actually deleted vs not found (not in this org, or already deleted) rather than erroring on a partial match. Use unpublish_article instead if you only want to stop serving a live post without losing the draft — that's reversible, this isn't.
article:write
POST
update_article
Edit a post: its title, its body (`content`, HTML) and/or its metadata. Only the fields you pass change. `metadataPatch` sets just the keys you give and leaves the rest (an atomic shallow merge; null deletes a key): summary (the dek), tags, owners, due_date, author / author_id, slug, meta_title, meta_description, published_url, template_key (this post's template over the collection's), mention_degree, direction_gate, paper_trail. `metadata` replaces the whole object instead. Title and body changes are refused while a writing tool (Auto-Pen, a kernel, the fact-check, positioning) holds the post. A body change opens a version (one per five minutes, as autosave does; get_article_history). The same edit as the app's editor.
article:write
POST
edit_article_with_prompt
Rewrite a post with AI from a plain-words instruction ('tighten the intro', 'make it warmer', 'add a section on pricing') and save the result as a new edit — the app's Prompt panel. Title and body may both change. Refused while a writing tool (Auto-Pen, a kernel, the fact-check, positioning) holds the post; usage-gated like every AI edit. Returns the updated post.
article:write
POST
run_mention_pass
Re-apply the brand-mention and competitor policy to one post, now: name the client where it fits, at the collection's strength (or the post's own mention_degree), scrub competitors and blocked companies. The same pass an Auto-Pen runs after writing, and the app's Brand mention button. Refused while a writing tool holds the post. The other per-post passes (visuals, links, metadata) are run_enhance_pass.
article:write
POST
restore_article_version
Restore a post to an earlier version (get_article_history): its title and body come back as a NEW latest version, and every version stays. Refused while a writing tool holds the post. Publishing it is a separate step (publish_article).
article:write
POST
resolve_comment_with_ai
Have AI make the change a comment asks for: it rewrites only the passage the comment is anchored to (its `quote`), saves the post, and resolves the thread. Refused when the comment quotes nothing, when that text is no longer in the post, when the rewrite would delete most of the post, or while a writing tool holds the post.
article:write
POST
cite_preferred_sources
Step 3: apply the latest FINISHED preferred-sources preview — exactly the chosen sentences get_preferred_sources_preview lists, nothing re-proposed. Each chosen post runs an enhance-only Auto-Pen whose external-sources pass puts the previewed sentence in (linked on the figure, never on the client's name), records the page for its Sources, and republishes; a post that changed since the preview is left alone, and exclude_article_ids leaves out chosen posts a reviewer decided against. On a manual-approval or client-CMS collection the change lands on the draft and waits (republishes: false). Returns as soon as the runs are queued; follow each with get_auto_pen_status.
article:write
POST
run_enhance_pass
Run one enhance pass on one post, now, whatever the collection's policy says: `visuals` (diagrams and the tables its template commissioned), `internal_links` (links to the collection's own pages, from its sitemap), `external_links` (citations to primary sources, under the org's source policy) or `metadata` (JSON-LD, meta tags, llms.txt). It runs the collection's enhance file (get_enhancer_file: the models and settings) and records the run (get_enhance_run): the version and models that ran, what it added, the outcome and its cost. A pass that cannot run says why (`skip_reason`: no_sitemap, sitemap_unreadable). The same pass an Auto-Pen runs after writing. The brand-mention pass is run_mention_pass.
article:write
POST
publish_article
Publish an article: pin its current content as the live published version, surfaced via list_published/get_published and the public published API. Blocks if the article has un-approved reviewers, or if the collection's Human Checkpoint is 'manual' (needs a signed-off reviewer) or 'review_window' (needs the window to close, or a signed-off reviewer to clear it early) — see get_collection_config's cadence.approval_mode. Returns the published version and timestamp.
article:publish
POST
schedule_article_publish
Publish a WRITTEN post on a chosen day: the article must be finished (or in review) and not yet live. It goes live that day at its collection's publish time and timezone, through the same run the Planner publishes with (local pin, CMS by the collection's publish control, site rebuild). Scheduling is the approval: the run is recorded as approved by the API key's user. Call again with another date to move it; publish_article publishes it now instead. A post the Planner wrote keeps its run and moves day; a post written by hand gets a run of its own.
article:publish
POST
unpublish_article
Unpublish an article: clear its published pointer so it stops being served via list_published/get_published and the public published API. Content and version history are untouched: the article just returns to a draft state. Idempotent.
article:publish
POST
list_social_profiles
The caller's social posting personas (profiles).
social_profile:read
POST
create_social_profile
Create a social posting persona for the caller's organization.
social_profile:write
POST
update_social_profile
Update a social posting persona's name, description, or style guidance.
social_profile:write
POST
delete_social_profile
Delete a social posting persona. Its posts detach to the Default persona.
social_profile:write
POST
list_article_comments
Every comment on a post — members' and guest reviewers' — top-level and replies (parent_id), oldest first, with the text each is anchored to (`quote`), whether it is resolved, and its author.
review:read
POST
list_article_reviewers
List an article's reviewers (org-member and guest) with each one's verdict, plus an aggregate approval summary. This is the same article_reviews data publish_article's un-approved-reviewer gate reads.
review:read
POST
list_standing_reviewers
List the caller's organization's standing reviewers (organization_standing_reviewers) — external contacts auto-invited as a guest reviewer on every article that reaches a finished, never-published draft. Every row is scoped to the calling key's own org.
review:read
POST
list_eligible_reviewers
List teammates in the caller's organization eligible to be assigned as a post's reviewer.
review:read
POST
list_assigned_reviews
The posts waiting on the key's user's review, across every collection: each unpublished post where they are an assigned reviewer and still have something to do — a verdict (submit_review), or publishing once everyone has approved (publish_article). Needing a verdict first. The "Needs you · Assigned to me" list on Letterpress → Letterbases. Empty for a key with no user.
review:read
POST
list_revisions
List proposed edits to already-live posts that are waiting for a decision — the Approvals queue in a collection's 'needs you' strip. Each revision is one post, with one change per edit: a client mention an AI citation earned (source flywheel, on a review-posture collection) or an internal link the nightly refresh would add (source relink, on a manual-approval or client-CMS collection). is_stale means the page moved since the proposal: accepts are refused until it is recomputed. Pass collection_id for one collection's queue. Decide with decide_revision.
review:read
POST
add_article_comment
Comment on a post as yourself, or reply to a top-level comment (parent_id). Anchor it to a passage with `quote` (the exact text) and a little context either side. kind `ai_esque` flags the quoted passage as reading like AI, and also sends it to the writing kernel as feedback. The app's comment box.
review:write
POST
update_article_comment
Change a comment: edit its text or @mentions (its author only), and/or resolve (resolved: true) or reopen (false) its thread (anyone in the org).
review:write
POST
delete_article_comment
Delete a comment — yours, or anyone's if you are an org admin. Its replies go with it. Resolving (update_article_comment) keeps the thread; this does not.
review:write
POST
add_article_reviewer
Add an org-member reviewer to an article (reviewer_id from list_org_members). The reviewer must be domain-eligible for the org. No notification is sent: the reviewer sees the request in-app.
review:write
POST
remove_article_reviewer
Remove a reviewer from an article (org-member or a pending guest's review row): the publish gate recomputes without them. To revoke a guest's access entirely (not just their review row), use revoke_guest_reviewer instead.
review:write
POST
submit_review
Submit your own verdict (approved or changes_requested) on an article you're an assigned reviewer for — the same publish_article un-approved-reviewer gate this clears. Only the review row whose reviewer_id matches the calling API key's creator can be submitted; a guest reviewer's row (no MCP equivalent — see list_article_reviewers) can't be submitted this way. Re-submittable.
review:write
POST
revoke_guest_reviewer
Revoke a guest reviewer's access to an article. Soft-revokes the access row (their existing comments are preserved) and clears their pending review row so the publish gate recomputes without them. Idempotent: revoking an already-revoked guest just clears the review row. Pure database mutation; nothing is sent to the guest.
review:write
POST
invite_guest_reviewer
Invite someone outside the org to review one post as a guest: they get an email with a link and a 6-digit PIN, can comment and approve or ask for changes, and need no account. Their pending review counts in the publish gate (list_article_reviewers) until they approve or you revoke them (revoke_guest_reviewer with the returned access.id). Returns the review link; the PIN only when the email could not be sent, so you can pass it on yourself — the same thing the app's Reviews panel shows. One live invite per email per post.
review:write
POST
add_standing_reviewer
Add a standing reviewer to the caller's organization. Adding one here writes only a row — no email is sent synchronously; the notify-standing-reviewers cron auto-invites this contact as a guest reviewer, asynchronously, the next time an article reaches a finished, never-published draft.
review:write
POST
remove_standing_reviewer
Remove a standing reviewer from the caller's organization. Stops FUTURE auto-invites only — never revokes an invite already sent to that email on an article already in review (use revoke_guest_reviewer for that).
review:write
POST
decide_revision
Accept or reject a proposed edit to a live post (from list_revisions) — the same decision the Approvals queue makes (lib/revisions/decide.ts). Pass change_id to decide one change; omit it to decide every undecided change at once (with an optional note). Once every change is decided the revision resolves; if any change was accepted and we publish the page, the accepted changes are applied and the post republished — applied says so. On a connector/advisory collection acceptance is recorded only (apply it in your CMS). A stale revision refuses accepts. Recorded against the API key's owner.
review:write